1 前言
锁的状态有4种,无锁,偏向锁、轻量级锁、重量级锁。那为什么会有锁升级的这种概念,其实大家都知道synchronized 在1.6之后做了升级,但具体是升级了什么?其实在jdk1.6之前锁只有重量级锁这个概念(但是重量级锁需要向内核申请额外的锁资源,涉及到用户态和内核态的切换,比较浪费资源)。升级了之后就不需要直接从无锁到重量级锁了,所以做了锁升级。
偏向锁:当只有一个线程争抢锁资源的时候,将线程拥有者标示为当前线程。
轻量级锁(自旋锁):一个或多个线程通过CAS去争抢锁,如果抢不到就一直自旋,知道超过自旋次数,升级为重量级锁。
重量级锁:多个线程争抢锁,向内核申请锁资源,将为抢到的锁放在队列中直接阻塞。
锁升级过程:
1 当只有一个线程a去竞争资源时,会先使用偏向锁,就是给一个标识,说明这个锁正在被线程a占有。
2 当线程a获取锁之后,线程b、线程c又来竞争锁,发现锁被线程a获取,于是撤销偏向锁的标识,线程a、b、c开始通过CAS去竞争锁
3 当其中一个线程获取锁之后(假如是a),然后线程b和线程c一直自旋获取锁,等到自旋到一定次数依然没有获取锁时,就将锁升级为重量级锁,向内核升级资源,直接将等待的线程阻塞。
2 对象在内存中的布局
对象头在主要包括两部分数据:Mark Word(标记字段)和 Klass Pointer(类型指针)
Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
其实synchronized
用的锁是存在对象头里的,具体是存在对象头的Mark Word中。那Mark Word在对象头中存储了什么呢?
64位的虚拟机:
32位的虚拟机:
可以发现锁标志位分别是:无锁(01),偏向锁(01),轻量级锁(00),重量级锁(10)。
3 代码演示锁升级过程
引入依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
测试代码:
public class TestB {
private byte test = 1;
public static void main(String[] args) {
TestB testB = new TestB();
System.out.println(ClassLayout.parseInstance(testB).toPrintable());
}
}
测试结果:
序号1:请求头mark Word占8个字节
序号2:请求头Klass Pointer占4个字节
序号3:成员变量信息,一个byte类型变量占一个字节,多出来的3个字节间隙填充补齐(alignment/padding gap)
序号4:成员变量信息,一个string类型占4个字节
序号5:补充4个字节变成8的倍数。
锁升级过程代码:
public class TestB {
static Object object = new Object();
public static class LockThread implements Runnable{
@Override
public void run() {
synchronized (object){
//进行一些耗时操作
String test="";
for (int i = 0; i < 1000; i++) {
test += "2";
}
}
}
}
public static void main(String[] args) {
//只有一个线程去抢占锁
new Thread(new LockThread()).start();
System.out.println("===========偏向锁===========");
System.out.println(ClassLayout.parseInstance(object).toPrintable());
//再加一个线程,两个线程去抢占锁
new Thread(new LockThread()).start();
System.out.println("===========轻量级锁===========");
System.out.println(ClassLayout.parseInstance(object).toPrintable());
//多个线程抢占,一直在自旋,升级位重量级锁
for (int i = 0; i < 20; i++) {
new Thread(new LockThread()).start();
}
System.out.println("===========重量级锁===========");
System.out.println(ClassLayout.parseInstance(object).toPrintable());
}
}
结果: