Java 并发编程之 synchronized

Synchronized 是 Java 中解决并发问题的一个关键字,Synchronized的可以保证线程互斥的访问同步代码、保证共享变量的修改能够及时可见、有效解决重排序问题。从语法上讲,Synchronized总共有三种用法:修饰普通方法、修饰静态方法、修饰代码块。

Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的。Monitor 底层依赖于操作系统的 Mutex Lock 来实现。但这种线程之间的上下文切换成本非常高,状态之间的转换需要相对比较长的时间,所以有时候 Synchronized 会比较低 。依赖于操作系统 Mutex Lock 所实现的锁称为重量级锁。JDK1.6 以后对 Synchronized 进行了很多优化,避免重量级锁的开销,通过引入轻量级锁和偏向锁实现锁的升级。

偏向锁

上偏向锁就是把 markword 的线程 ID 改为自己线程 ID 的过程,下次同一个线程加锁的时候只需要判断线程 io 是否为自身。

偏向锁有个时延,默认是4秒。因为 JVM 自身有一些默认启动的线程,里面也有很多同步代码,这些代码在 JVM 启动时 JVM 知道一定会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁撤销和锁升级的操作,效率较低。可通过 -XX:BiasedLockingStartupDelay=0 来设置。

如果有线程竞争,会撤销偏向锁升级轻量级锁,线程在自己的线程栈生成 LockRecord ,用 CAS 操作将 markword 设置为指向自己这个线程的 LR 的指针,设置成功者得到锁。偏向锁由于有锁撤销的过程 revoke,会消耗系统资源,所以,在锁争用特别激烈的时候,用偏向锁未必效率高。还不如直接使用轻量级锁。

轻量级锁

有线程超过10次自旋, -XX:PreBlockSpin, 或者自旋线程数超过CPU核数的一半, 1.6之后,加入自适应自旋 Adapative Self Spinning 。自适应自旋锁意味着自旋的次数不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。