【吊打面试官】90%Java面试中锁问题的解决妙招(下)

点赞关注,不会迷路!   

前言

干货分享——Java中的锁问题 后续解决方法更新啦!不知道你们之前有没有想到呢?快来看看吧

分析结果3:

1-----9行是没有进行hashcode之前的对象头信息,可以看到1-7B的56bit没有值,打 印完hashcode之后16----21行就有值了,为什么是1-7B,不是0-6B呢?因为是小端存 储。其中12行是我们通过hashcode方法打印的结果,13行是我根据1-7B的信息计算出来 的hashcode,所以可以确定java对象头当中的mark work里面的后七个字节存储的是 hashcode信息,那么第一个字节当中的八位分别存的就是分带年龄、偏向锁信息,和对象 状态,这个8bit分别表示的信息如下图(其实上图也有信息),这个图会随着对象状态改变 而改变,下图是无锁状态下
在这里插入图片描述
关于对象状态一共分为五种状态,分别是无锁、偏向锁、轻量锁、重量锁、 GC标记,那么2bit,如何能表示五种状态(2bit最多只能表示4中状态分别是: 00,01,10,11),jvm做的比较好的是把偏向锁和无锁状态表示为同一个状态, 然后根据图中偏向锁的标识再去标识是无锁还是偏向锁状态。什么意思呢?写个 代码分析一下,在写代码之前我们先记得无锁状态下的信息00000001,然后 写一个偏向锁的例子看看结果

Java代码和输出结果:

1 package com.luban.layout; 
2 import org.openjdk.jol.info.ClassLayout; 
3 import static java.lang.System.out; 
4
5 public class JOLExample2 {
6 static A a; 
7 public static void main(String[] args) throws Exception { 
8 //Thread.sleep(5000); 
9 a = new A(); 
10 out.println("befre lock"); 
11 out.println(ClassLayout.parseInstance(a).toPrintable()); 
12 sync(); 
13 out.println("after lock"); 
14 out.println(ClassLayout.parseInstance(a).toPrintable()); 
15 } 
16
17 public static void sync() throws InterruptedException { 
18 synchronized (a){ 
19 System.out.println("我也不知道要打印什么"); 
20 } 
21
22 } 
23 }
1 befre lock 
2 com.luban.layout.A object internals: 
3 OFFSET SIZE TYPE DESCRIPTION VALUE 
4 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 
5 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
6 8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (5 36920387) 
7 12 1 boolean A.flag false 
8 13 3 (loss due to the next object alignment) 
9 Instance size: 16 bytes 
10 Space losses: 0 bytes internal + 3 bytes external = 3 bytes total 
11
12 我也不知道要打印什么 
13 after lock 
14 com.luban.layout.A object internals: 
15 OFFSET SIZE TYPE DESCRIPTION VALUE 
16 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 
17 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
18 8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387) 
19 12 1 boolean A.flag false 
20 13 3 (loss due to the next object alignment)

分析结果4

上面这个程序只有一个线程去调用sync方法,故而讲道理应该是偏向锁,但 是你会发现输出的结果(第一个字节)依然是00000001和无锁的时候一模一 样,其实这是因为虚拟机在启动的时候对于偏向锁有延迟,比如把上述代码当中 的睡眠注释掉结果就会不一样了,结果会变成00000101当然为了方便测试我 们可以直接通过JVM的参数来禁用延迟-XX:+UseBiasedLocking - XX:BiasedLockingStartupDelay=0,想想为什么偏向锁会延迟?(除了这 8bit,其他bit存储了线程信息和epoch,这里不截图了),需要注意的after lock,退出同步后依然保持了偏向信息
在这里插入图片描述

性能对比偏向锁和轻量级锁:

1 package com.luban.layout; 
2 public class A { 
3 int i; 
4 public synchronized void parse(){ 
5 i++; 
6 } 
7 }
1 package com.luban.layout; 
2 import org.openjdk.jol.info.ClassLayout;
3 import static java.lang.System.out; 
4 //‐XX:BiasedLockingStartupDelay=20000 ‐XX:BiasedLockingStartupDelay=0 
5 public class JOLExample3 { 
6 public static void main(String[] args) throws Exception { 
7 A a = new A(); 
8 long start = System.currentTimeMillis(); 
9 //调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能 
10 //如果不出意外,结果灰常明显 
11 for(int i=0;i<1000000000L;i++){ 
12 a.parse(); 
13 } 
14 long end = System.currentTimeMillis(); 
15 System.out.println(String.format("%sms", end ‐ start)); 
16
17 } 
18
19 }

那么问题来了,什么是轻量级锁呢?工作原理是什么呢?为什么比偏向锁 慢?轻量级锁尝试在应用层面解决线程同步问题,而不触发操作系统的互斥操 作,轻量级锁减少多线程进入互斥的几率,不能代替互斥

首先看一下轻量级锁的对象头

1 package com.luban.layout; 
2 import org.openjdk.jol.info.ClassLayout; 
3 import static java.lang.System.out; 
4
5 public class JOLExample5 { 
6 static A a; 
7 public static void main(String[] args) throws Exception { 
8 a = new A(); 
9 out.println("befre lock"); 
10 out.println(ClassLayout.parseInstance(a).toPrintable()); 
11 sync(); 
12 out.println("after lock"); 
13 out.println(ClassLayout.parseInstance(a).toPrintable()); 
14 } 
15
16 public static void sync() throws InterruptedException {
17 synchronized (a){ 
18 out.println("lock ing"); 
19 out.println(ClassLayout.parseInstance(a).toPrintable()); 
20 } 
21 } 
22 }

性能对比轻量对比重量:

1 package com.luban.layout; 
2
3 import java.util.concurrent.CountDownLatch; 
4
5 public class JOLExample4 { 
6 static CountDownLatch countDownLatch = new CountDownLatch(1000000000); 
7 public static void main(String[] args) throws Exception { 
8 final A a = new A(); 
9
10 long start = System.currentTimeMillis(); 
11
12 //调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能 
13 //如果不出意外,结果灰常明显 
14 for(int i=0;i<2;i++){ 
15 new Thread(){ 
16 @Override 
17 public void run() { 
18 while (countDownLatch.getCount() > 0) {
19 a.parse(); 
20 } 
21 } 
22 }.start(); 
23 } 
24 countDownLatch.await(); 
25 long end = System.currentTimeMillis(); 
26 System.out.println(String.format("%sms", end ‐ start));
27
28 } 
29
30 }

在这里插入图片描述

关于重量锁首先看对象头

1 package com.luban.layout; 
2 import org.openjdk.jol.info.ClassLayout; 
3 import static java.lang.System.out; 
4
5 public class JOLExample6 { 
6 static A a; 
7 public static void main(String[] args) throws Exception {
8 //Thread.sleep(5000); 
9 a = new A(); 
10 out.println("befre lock"); 
11 out.println(ClassLayout.parseInstance(a).toPrintable()); 
12
13 Thread t1= new Thread(){ 
14 public void run() { 
15 synchronized (a){ 
16 try { 
17 Thread.sleep(5000); 
18 System.out.println("t1 release"); 
19 } catch (InterruptedException e) { 
20 e.printStackTrace(); 
21 } 
22 } 
23 } 
24 }; 
25 t1.start(); 
26 Thread.sleep(1000); 
27 out.println("t1 lock ing"); 
28 out.println(ClassLayout.parseInstance(a).toPrintable()); 
29 sync(); 
30 out.println("after lock"); 
31 out.println(ClassLayout.parseInstance(a).toPrintable()); 
32
33 System.gc(); 
34 out.println("after gc()"); 
35 out.println(ClassLayout.parseInstance(a).toPrintable()); 
36 } 
37
38 public static void sync() throws InterruptedException { 
39 synchronized (a){ 
40 System.out.println("t1 main lock"); 
41 out.println(ClassLayout.parseInstance(a).toPrintable()); 
42 } 
43 } 
44 }

如果调用wait方法则立刻变成重量锁

1 package com.luban.layout; 
2
3 import org.openjdk.jol.info.ClassLayout; 
4
5 import static java.lang.System.out; 
6
7 public class JOLExample7 { 
8 static A a; 
9 public static void main(String[] args) throws Exception { 
10 //Thread.sleep(5000); 
11 a = new A(); 
12 out.println("befre lock"); 
13 out.println(ClassLayout.parseInstance(a).toPrintable()); 
14
15 Thread t1= new Thread(){ 
16 public void run() { 
17 synchronized (a){ 
18 try { 
19 synchronized (a) { 
20 System.out.println("before wait");
21 out.println(ClassLayout.parseInstance(a).toPrintable()); 
22 a.wait(); 
23 System.out.println(" after wait"); 
24 out.println(ClassLayout.parseInstance(a).toPrintable()); 
25 }
26 } catch (InterruptedException e) {
27 e.printStackTrace(); 
28 } 
29 } 
30 } 
31 }; 
32 t1.start(); 
33 Thread.sleep(5000); 
34 synchronized (a) { 
35 a.notifyAll(); 
36 } 
37 } 
38 }

需要注意的是如果对象已经计算了 hashcode就不能偏向了

1 package com.luban.layout; 
2 import org.openjdk.jol.info.ClassLayout; 
3 import static java.lang.System.out; 
4
5 public class JOLExample8 { 
6 static A a; 
7 public static void main(String[] args) throws Exception { 
8
9 Thread.sleep(5000); 
10 a = new A(); 
11 a.hashCode(); 
12 out.println("befre lock"); 
13 out.println(ClassLayout.parseInstance(a).toPrintable()); 
14
15 Thread t1= new Thread(){ 
16 public void run() { 
17 synchronized (a){ 
18 try { 
19 synchronized (a) {
20 System.out.println("lock ed"); 
21 out.println(ClassLayout.parseInstance(a).toPrintable()); 
22 }
23 } catch (Exception e) { 
24 e.printStackTrace(); 
25 } 
26 }
27 } 
28 }; 
29 t1.start();
30
31 } 
32 }

猜你喜欢

转载自blog.csdn.net/weixin_50333534/article/details/109321671