偏向锁(Biased Lock
)也是一种非互斥锁,它比自旋锁更轻,是Java为了解决synchronized重量级锁的性能问题而在JDK1.6和自旋锁一起引入的,是Java中特有的一种锁。
偏向锁需要启用才可以使用,当然JDK1.6及以后已经默认启用了偏向锁,而且是在JVM启动后四秒才启用。当启用了偏向锁之后,对象需要加上偏向锁时,线程会获取对象头中的markword
字段(Java对象的结构我们前天已经讲过了,不记得的同学去复习一下吧),如果是可偏向状态(对象处于匿名偏向状态,锁标志位为01
,偏向锁标志位为1
,并且markword中没有其他线程的指针),就使用我们昨天讲过的CAS
操作在对象的markword字段中标记这个线程的id,表示这个线程获得了该对象的偏向锁。偏向锁并不会自动撤销,而是在其他对象尝试获取这个偏向锁之前一直保持原来的状态,也就是markword中存储指向线程的指针的状态,这样该线程下一次尝试获取这个偏向锁的时候,就会直接获取到而不用修改对象。
当然,如果在线程使用CAS操作尝试获取偏向锁时失败了,就说明有另一个线程已经抢先获取了锁,这时需要撤销该偏向锁,并且升级为轻量级锁。注意:撤销偏向锁的操作需要等到JVM safepoint
(全局安全点,这个有点类似于STW
,此时所有工作线程都被挂起,没有线程在执行字节码)的到来才可以执行。这也是为什么启用偏向锁不一定能提高性能——因为偏向锁的撤销需要等待全局安全点,而在大并发的情况下,线程之间对锁的竞争非常激烈,偏向锁的撤销和添加操作都会带来很大的资源消耗,这反而比直接使用轻量级锁更消耗资源。这也是为什么偏向锁默认情况下要在JVM启动之后4秒才开启,因为JVM初始化代码中有大量的同步方法需要获取锁,而此时线程之间的竞争就非常激烈。综上所述,开启偏向锁并不一定可以提高性能。
那你可能要问了,偏向锁加上了之后,其他线程就没有办法再去获取这个偏向锁了吗?Java的开发者肯定也想到了这个问题,所以有了偏向锁的批量再偏向(Bulk Rebias)机制。简单来说,就是一个通过时间戳来判断偏向锁有效性并重新加偏向锁的机制。因为它比较复杂,一时半会也说不完,而且面试也不会问,所以咱们这次就只提这么一句,面试一般也不会问这么细的,如果大家对这个感兴趣,咱们以后再讲。
注:由于偏向锁要在对象markword中记录指向线程的指针信息,所以markword中存放对象hashcode的空间会被占用。此时如果该对象要计算identity hashcode
(没有被重写的hashcode),该对象所持有的偏向锁会被撤销,并直接膨胀为重量级锁。