java锁膨胀过程
Posted 哎喔别走
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java锁膨胀过程相关的知识,希望对你有一定的参考价值。
今天看了一个视频教程,其中讲到了锁膨胀的过程,这个课程通过一个java main很容易的让人理解synchronized的优化以及锁膨胀的过程。在上代码之前先上张对象头markword的图片如下:
代码如下(主线程再加上新起的两个线程,三个线程同步锁stepNum导致锁升级,由偏向锁到轻量级锁,到重量级锁):
import org.openjdk.jol.info.ClassLayout; public class LockEvolutionShow { public static void main(String[] args) { Integer initNum = new Integer("1");// 初始化一个对象,默认是无锁状态(状态为non-biasable)最后三位是001,注意打印的结果是16进制,转为二进制即可 System.out.println("【non-biasable】===对象头MarkWord末尾三位【001】=" + ClassLayout.parseInstance(initNum).toPrintable()); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } Integer stepNum = new Integer("2");// JVM默认启动后等待3s后就会把对象标记为偏向锁(状态为biasable),此时打印结果可以看到后三位是101。,注意打印的结果是16进制,转为二进制即可 System.out.println("【biasable】===对象头MarkWord末尾三位【101】=" + ClassLayout.parseInstance(stepNum).toPrintable()); // 添加同步原语后,可以看到对象被标记为偏向锁,注意打印的后三位是101(状态为biased),注意打印的结果是16进制,转为二进制即可 synchronized (stepNum) { System.out.println( "【biased lock】===对象头MarkWord末尾三位【101】=" + ClassLayout.parseInstance(stepNum).toPrintable()); } System.out.println(// 退出同步块后,可以看到对象依然被标记为偏向锁(状态为biased),并未清除,此时打印结果可以看到后三位是101。,注意打印的结果是16进制,转为二进制即可 "【biased lock release]===对象头MarkWord末尾三位【101】=" + ClassLayout.parseInstance(stepNum).toPrintable()); new Thread(() -> {// 启动了一个线程,线程的并发特性(上面的主线程和这个新起的线程并发,同步锁stepNum),导致锁升级,这时候升级为轻量级锁(状态为thin // lock),此时打印结果可以看到后两位是00。,注意打印的结果是16进制,转为二进制即可 synchronized (stepNum) { System.out.println( "【thin lock】===对象头MarkWord末尾两位【00】=" + ClassLayout.parseInstance(stepNum).toPrintable()); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // 注意这个线程保持锁至少5s,导致下面又新起的线程同步锁stepNum,再次锁升级,由轻量级锁thin变为重量级fat,此时打印结果后两位是10。 System.out.println("【thin lock -> fat lock】===对象头MarkWord末尾两位【10】=" + ClassLayout.parseInstance(stepNum).toPrintable()); } }).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { synchronized (stepNum) { ////注意这个线程同步锁stepNum,导致锁升级,由轻量级锁thin变为重量级fat,此时打印结果后两位是10。 System.out.println( "【fat lock】===对象头MarkWord末尾两位【10】=" + ClassLayout.parseInstance(stepNum).toPrintable()); } }).start(); } }
【non-biasable】===对象头MarkWord末尾三位【001】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 1
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
【biasable】===对象头MarkWord末尾三位【101】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
【biased lock】===对象头MarkWord末尾三位【101】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000000025ba005 (biased: 0x00000000000096e8; epoch: 0; age: 0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
【biased lock release]===对象头MarkWord末尾三位【101】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000000025ba005 (biased: 0x00000000000096e8; epoch: 0; age: 0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
【thin lock】===对象头MarkWord末尾两位【00】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001f08f6f0 (thin lock: 0x000000001f08f6f0)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
【thin lock -> fat lock】===对象头MarkWord末尾两位【10】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001c0f7b4a (fat lock: 0x000000001c0f7b4a)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
【fat lock】===对象头MarkWord末尾两位【10】=java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000000001c0f7b4a (fat lock: 0x000000001c0f7b4a)
8 4 (object header: class) 0xf80022ad
12 4 int Integer.value 2
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
PS:头部markword 8个字节,初始状态【fat lock】的value是16进制0x000000001c0f7b4a ,转为二进制是11100000011110111101101001010,末尾是10对照上面的markword图片可知是重量级锁。
java-锁膨胀的过程
先来看个奇怪的demo
public class A { int i=0; // boolean flag =false; public synchronized void parse(){ i++; JOLExample6.countDownLatch.countDown(); } }
睡眠5秒,测试
public class JOLExample3 { static A a; public static void main(String[] args) throws Exception { Thread.sleep(5000); a= new A(); //a.hashCode(); out.println("befor lock"); out.println(ClassLayout.parseInstance(a).toPrintable());//无锁:偏向锁? synchronized (a){ out.println("lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } out.println("after lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); } }
我注释的那行代码是什么锁?看下结果
可以看出,没有线程持有锁的时候,是可偏向状态
然后我们把睡眠的代码注释掉,再测试一下
//Thread.sleep(5000);
看下结果
再看个两个线程的demo
首先是两个线程交替执行:
public class JOLExample10 { static A a; public static void main(String[] args) throws Exception { a= new A(); Thread t1 = new Thread(){ @Override public void run() { synchronized (a){ out.println("t1 lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } }; t1.start(); Thread.sleep(10000);//睡眠10秒,让main线程和t1线程交替执行 synchronized (a){//a b c c+++ out.println("main lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } out.println("after lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); } }
看下结果
可以看出,交替执行时,是轻量锁
我们把睡眠的代码注释掉
//Thread.sleep(5000);//睡眠10秒,让main线程和t1线程交替执行
再次测试,
public class JOLExample10 { static A a; public static void main(String[] args) throws Exception { a= new A(); Thread t1 = new Thread(){ @Override public void run() { synchronized (a){ out.println("t1 lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } }; t1.start(); //Thread.sleep(5000);//睡眠10秒,让main线程和t1线程交替执行 synchronized (a){//a b c c+++ out.println("main lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } Thread.sleep(5000);//睡眠10秒 out.println("after lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); } }
看下结果
自旋
自旋一段时间,可以理解为空转,时间很短,具体时间需要看jvm源码,如果在自旋时间内拿到了锁,就不再膨胀,如果还是拿不到锁,则膨胀为重量锁,如下
public static void main(String[] args) throws Exception { a= new A(); Thread t1 = new Thread(){ @Override public void run() { synchronized (a){ out.println("t1 lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } }; t1.start(); Thread.sleep(1670);//睡眠10秒,让main线程和t1线程交替执行 synchronized (a){//自旋一段时间,可以理解为时间很短,具体时间需要看jvm源码,如果在自旋时间内拿到了锁,就不再膨胀,如果还是拿不到锁,则膨胀为重量锁 out.println("main lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } Thread thread2 = new Thread(){ @Override public void run() { synchronized (a){ out.println("t2 lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } }; thread2.start(); /*Thread.sleep(10); synchronized (a){ out.println("main lock ing"); out.println(ClassLayout.parseInstance(a).toPrintable()); }*/ //Thread.sleep(5000);//睡眠10秒,让main线程和t1线程交替执行 out.println("after lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); }
如果调用wait方法,则立即变成重量锁
看下demo
public class JOLExample11 { static A a; public static void main(String[] args) throws Exception { //Thread.sleep(5000); a = new A(); out.println("befre lock"); out.println(ClassLayout.parseInstance(a).toPrintable()); Thread t1= new Thread(){ public void run() { synchronized (a){ try { synchronized (a) { System.out.println("before wait"); out.println(ClassLayout.parseInstance(a).toPrintable()); a.wait();//如果调用wait方法,则立即变成重量锁 System.out.println(" after wait"); out.println(ClassLayout.parseInstance(a).toPrintable()); } } catch (InterruptedException e) { e.printStackTrace(); } } } }; t1.start(); Thread.sleep(7000); synchronized (a) { a.notifyAll(); } } }
看下结果
我们再看个synchronized膨胀的奇怪特性
让偏向锁无延迟启动
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
public class JOLExample12 { static List<A> list = new ArrayList<A>(); public static void main(String[] args) throws Exception { Thread t1 = new Thread() { public void run() { for (int i=0;i<10;i++){ A a = new A(); synchronized (a){ System.out.println("111111"); list.add(a); } } } }; t1.start(); t1.join(); out.println("befre t2"); //偏向 out.println(ClassLayout.parseInstance(list.get(1)).toPrintable()); Thread t2 = new Thread() { int k=0; public void run() { for(A a:list){ synchronized (a){ System.out.println("22222"); if (k==4){ out.println("t2 ing"); //轻量锁 out.println(ClassLayout.parseInstance(a).toPrintable()); } } k++; } } }; t2.start(); } }
t1线程new10个对象,t2线程取第五个,看下结果
我们把new对象的数量改一下,改成20个,再来试一下
public class JOLExample12 { static List<A> list = new ArrayList<A>(); public static void main(String[] args) throws Exception { Thread t1 = new Thread() { public void run() { for (int i=0;i<20;i++){ A a = new A(); synchronized (a){ System.out.println("111111"); list.add(a); } } } }; t1.start(); t1.join(); out.println("befre t2"); //偏向 out.println(ClassLayout.parseInstance(list.get(1)).toPrintable()); Thread t2 = new Thread() { int k=0; public void run() { for(A a:list){ synchronized (a){ System.out.println("22222"); if (k==19){ out.println("t2 ing"); //轻量锁 out.println(ClassLayout.parseInstance(a).toPrintable()); } } k++; } } }; t2.start(); } }
这里我们取第20个对象,查看对象头信息
我们可以看到,居然重偏向了,这里是jvm做的优化,20次以后就会冲偏向,小于20次时膨胀为轻量锁
这里我们称之为批量偏向,下面我们看下这个的原理
最后总结一下:轻量锁释放的时候将mark word重置为无锁状态,附上网络上的图
以上是关于java锁膨胀过程的主要内容,如果未能解决你的问题,请参考以下文章