java多线程锁解析(一)
首先,先来看一个最简单的java对象在jvm中加载后的二进制分布,简单展示一下代码
**
* @Author xuzzhh
* @Date 2021/9/12 16:20
* @Version 1.0
* @Since study
*/
public class ObjectHead {
static LHead lHead = new LHead();
public static void main(String[] args){
System.out.println("这是原有的对象头"+ClassLayout.parseInstance(lHead).toPrintable())}
}
}
public class LHead {
}
执行效果如图所示
A区域中包含了未使用(1)+对象年龄(4)+是否可偏向(1)+锁类型(2) 途中00000101 表示对象年龄为0,可偏向,01表示偏向锁(java对象类型初始化时都是01偏向锁) 而在紧接A区域后边的地方会有两种情况: (1):对象如果计算HashCode后的Hash值 (2):加锁后的持锁的线程ID 演示代码如下:
public class ObjectHead {
static LHead lHead = new LHead();
public static void main(String[] args) {
System.out.println("这是原有的对象头" + ClassLayout.parseInstance(lHead).toPrintable());
synchronized (lHead) {
System.out.println(ClassLayout.parseInstance(lHead).toPrintable());
System.gc();
System.out.println(ClassLayout.parseInstance(lHead).toPrintable());
}
System.out.println("这是Hash value" + Integer.toHexString(lHead.hashCode()));
System.out.println("这是Hash 对象头" + ClassLayout.parseInstance(lHead).toPrintable());
}
}
执行效果如下图: 从图上可以看出四种情况对应的执行结果, (1):第一种为正常初始化后的对象头0(未使用) 0000(对象年龄为0) 1(可偏向) 01(偏向锁) ,线程ID或者Hashcode均为0。 (2):第二种执行结果为加锁后的执行结果,加锁后的对象结果变为对象头0(未使用) 0000(对象年龄为0) 1(可偏向) 01(偏向锁),与第一次执行结果不同的是,第二次出现了线程持有ID(00101000 01101100 00000011)对应的十六进制地址为(28 6c 03),这表示这个对象已经被一个线程持有锁了,并且从头部对象01可知,持有的是偏向锁。 (3):第三种执行结果是在二的情况下,手进行了一次GC,所以其对象的年龄进行了加1,其对象头的结果变为对象头0(未使用) 0001(对象年龄为1) 1(可偏向) 01(偏向锁),其余效果相对第二种情况无变化。 (4):第四种情况为执行了HashCode计算后的对象头0(未使用) 0001(对象年龄为1) 0(不可偏向) 01(偏向锁),计算了HashCode后,00110111 01101100 0110010000111101 后边的线程持有ID也变成了HashCode,将二进制转换为16进制后是37 6c 64 3d 由于计算机使用小端存储,所以实际的HashCode值为3d646c37,可以看见,计算HashCode后,对象头中的是否可偏向变成了不可偏向,而线程持有ID则变成了HashCode的值。 总结: (1):当一把锁第一次进行加载的时候,对象头中出现的是偏向锁,当线程再次持有锁的时候依然会是偏向锁。 (2):当两个线程线程1和线程2交替执行时,对象头中的锁会膨胀为轻量级锁, 注:对象如果计算过HashCode后,可偏向标志为0,则表示不可偏向,此时即使是单个线程首次加锁,或者是两个线程交替执行持有对象锁,该锁依然会膨胀为轻量锁,所以在一般情况需要使用到偏向锁的时候,尽量不要对对象进行HashCode计算。 (3):当两个线程发生了资源竞争的时候,锁会膨胀为重量锁。 验证会在下一次更新中。
|