【吊打面試官】90%Java面試中鎖問題的解決妙招(下)

2020-10-29 11:01:23

點贊關注,不會迷路!   

前言

乾貨分享——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 }