目录
一、什么是偏向锁?
二、偏向锁原理
三、偏向锁演示
四、偏向锁的处理流程
五、偏向锁的撤销
六、偏向锁的好处
一、什么是偏向锁?
HotSpot作者经过研究实践发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。
偏向锁的“偏”,它的意思是锁会偏向于第一个获得它的线程,会在对象头(Mark Word中)存储锁偏向的线程ID,以后该线程进入和退出同步块时只需要检查是否为偏向锁、锁标志位以及ThreadID即可。
如下图是偏向锁对象头MarkWord布局:
二、偏向锁原理
偏向锁总是被第一个占用它的线程拥有,这个线程就是锁的偏向线程。在偏向锁的MarkWord中,就有一块区域用来记录偏向线程的ID,这个是在锁第一次被拥有的时候记录的。
因为记录了偏向线程ID,那么后续如果这个偏向线程进入和退出同步代码块的时候,就不需要再次加锁和解锁。
偏向锁再次进入同步代码块,是如何保证不需要再次加锁的?
偏向锁的线程会检查锁对象的MarkWord中是不是存放着自己的线程ID。
- 相等
表示偏向锁现在就是偏向当前线程的,无需再尝试获得锁。以后每次同步,都会检查锁的偏向线程ID与当前线程ID是否一致,一致就直接进入同步,省去了CAS去更新对象头的操作,提高了锁的性能。
- 不相等
表示偏向锁现在不是偏向当前线程的,此时发生了竞争,这时候当前线程就会尝试CAS去更新锁对象MarkWord中线程ID,尝试指定为自己的线程。此时也有两种情况:
1)、更新成功:表示已经将锁对象MarkWord中的线程ID替换为自己的线程ID,之前的线程可能刚好运行完,这时候锁重新偏向为当前线程,这种情况下,不会发生锁升级,锁仍然为偏向锁,只是偏向线程换成了另外一个线程;
2)、更新失败:表示之前的线程还在持有锁,那么当前线程只能一直CAS,当达到一定次数后,如果还没CAS成功,那么就会发生【偏向锁 -> 轻量级锁】的锁升级过程。
注意,偏向锁只有遇到其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁的。
三、偏向锁演示
public class BiasedLockDemo01 {
public static void main(String[] args) {
Object objLock = new Object();
new Thread(() -> {
synchronized (objLock) {
System.out.println(ClassLayout.parseInstance(objLock).toPrintable());
}
}, "t1").start();
}
}
如上我们看到,markword的倒数三位是000,根据前面的图,000表示的是轻量级锁,此时只有一个线程访问,为什么输出来的不是偏向锁标识101呢?
原因其实是偏向锁在Java 6之后是默认启用的,但在应用程序启动几秒钟(默认延迟4秒)之后才会激活,可以使用 -XX:BiasedLockingStartupDelay=0参数关闭延迟,让其在程序启动时立刻启动。当然为了演示,也可以在程序中休眠5秒,等待偏向锁激活后。
下面我们添加运行时JVM参数,再次启动程序,观察内存布局:
可以看到关闭偏向锁延迟后,当前锁就是偏向锁了。
四、偏向锁的处理流程
当线程第一次访问同步块并获取锁时,偏向锁处理流程如下:
- 1)、虚拟机将会把对象头中的是否偏向锁标志位设为“1”,即偏向模式;
- 2)、使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中 ,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,效率极高;
五、偏向锁的撤销
前面提到,大部分情况下,锁都是被同一个线程获取到,持有偏向锁的线程不会主动释放锁。那么大部分情况下,不会涉及到偏向锁的撤销,当有另外的线程尝试竞争偏向锁的时候,这个时候才会涉及偏向锁的撤销流程。
举个例子,有线程A、线程B两个线程竞争获取锁,锁大部分情况下都被线程A持有,此时锁偏向于线程A,这样线程A每次进入/退出同步代码块,都无需再次加锁,只需判断MarkWord中保存的线程ID是否等于自己的线程ID。
线程A不会主动释放偏向锁,当运行了一段时间后,突然线程B过来尝试竞争这个偏向锁了,此时持有偏向锁的线程A会发生偏向锁的撤销。
偏向锁的撤销需要等待全局安全点(这个时间点没有代码正在执行),同时会检查持有偏向锁的线程A是否还在执行:
1)、线程A还是同步代码块中运行:发生【偏向锁 -> 轻量级锁】的升级过程。此时轻量级锁由原持有偏向锁的线程A持有,继续执行其同步代码,而正在竞争的线程B会在外面CAS自旋等待获取这个轻量级锁。
2)、线程A刚好执行完成同步代码块:则会将对象头MarkWord设置为无锁状态并撤销偏向锁,然后锁重新偏向到线程B,注意,这里会修改MarkWord中线程ID保存为线程B的线程ID。
流程图大体如下:
六、偏向锁的好处
偏向锁是在只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一个锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。它并不一定总是对程序运行有利,如果程序中大多数的锁总是被多个不同的线程访问比如线程池,那偏向模式就是多余的,反而会影响效率。
注意,在JDK15中,已经废弃偏向锁。目前还是使用JDK1.8居多,所以我们还是有必要了解一下偏向锁。