Synchronized锁升级之无锁和偏向锁
为了优化synchronized锁的效率,在JDK1.6中,HotsPot虚拟机开发团队提出了锁升级的概念,包括偏向锁、轻量级锁、重量级锁,锁的升级值的是 无锁态 >> 偏向锁 >> 轻量级锁 >> 重量级锁
synchronized同步锁相关信息保存在锁对象的对象同中的Mark Word中,锁升级功能主要是依赖Mark Word中锁的标准为何是否偏向锁来实现的
从上图我们可以看到,无锁态对应的锁标志位位 01,是否偏向锁标志位 0,下面通过两个简单的Java case来演示无锁态和轻量级锁
因为要查看代码运行时的内存布局分析,所以在构建的maven工程中需要加入如下依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
1.无锁态
下面用一段代码来演示什么是无锁态
- Java code
public class Main {
public static void main(String[] args) {
Object objLock = new Object();
// 需要注意,只有调用了hashCode(),对象头中的MarkWord才会保存对应的hashCode值,否则全部是0
System.out.println("10进制: " + objLock.hashCode());
System.out.println("2进制: " + Integer.toBinaryString(objLock.hashCode()));
System.out.println("16进制: " + Integer.toHexString(objLock.hashCode()));
System.out.println(ClassLayout.parseInstance(objLock).toPrintable());
}
}
-
代码运行时输出的结果
-
分析
Mark Word对象头共站8个字符,共64位,也就是上图中的 1 ~ 8
,从后往前拼起来
00000000 00000000 00000000 01100111 01110011 00100111 10110110 00000001
Mark Word在64位JVM中内部结构中
- 红色字体部分:25位,不使用
- 蓝色字体部分:31位,表示对象的hashCode(可以看到与代码输出结构中的二进制hashCode是一样的)
- 橙色字体部分:1位,不使用
- 绿色字体部分:4位,表示对象的分代年龄(对象的最大分代年龄是15,占用的四个字节的最大是 1111 ,将其换成10进制即是 15)
- 紫色字体部分:1位,偏向锁标准为(0:不是,1:是)
- 黄色字体部分:2位,锁的标志位,按照上图中的这里是无锁态(001无锁态,101偏向锁,000轻量级锁,010重量级锁,011GC标识)
2.偏向锁
注意事项
偏向锁的启用时机,偏向锁在程序运行时需要进行 4s 的加载,如果程序在运行时,线程还没有去访问锁前程序的运行时间已经超过了4s,那么此时这把锁将会从无锁态升级到偏向锁
2.1.偏向锁的原理
-
在偏向锁第一次被线程拥有的时候,在偏向锁的Mark Word中,有一块区域用来记录偏向锁的ID,如下图所示
-
偏向锁的好处是什么?
- 假设现在的锁对象已经是一个偏向锁,偏向锁它会偏向于第一个获取锁的线程,并且在锁对象的Mark Word中会将该线程的线程ID保存起来,当该线程再来访问锁的时候,会先判断当前访问的线程ID是否是Mark Word中记录的线程ID,如果是将会不进行加锁,该线程直接执行同步块中的代码,这样省去了CAS去更新对象头的操作,提升了锁的性能
-
偏向锁再次进入同步代码块,如果保证不需要再次加锁?
2.2.偏向锁的撤销
注意事项
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁
在大部分情况下,锁都是被同一个线程获取到的,持有偏向锁的线程不会主动释放锁。那么大部分情况下不会涉及到偏向锁的撤销,当有另外的线程尝试竞争偏向锁的时候,这个时候才会涉及偏向锁的撤销流程
暂时无法在飞书文档外展示此内容
2.3.Java case
public class Main {
public static void main(String[] args) throws InterruptedException {
// 在这里sleep 5s,等待偏向锁加载成功
Thread.sleep(6000);
Object objLock = new Object();
synchronized (objLock){
System.out.println(ClassLayout.parseInstance(objLock).toPrintable());
}
}
}