jdk1.8源碼Synchronized及其實現原理


 一、Synchronized的基本使用

關於Synchronized在JVM的原理(偏向鎖,輕量級鎖,重量級鎖)可以參考 :   http://www.cnblogs.com/dennyzhangdd/p/6734638.html

Synchronized是Java中解決並發問題的一種最常用的方法,也是最簡單的一種方法。

Synchronized的作用主要有三個:

(1)確保線程互斥的訪問同步代碼

(2)保證共享變量的修改能夠及時可見

(3)有效解決重排序問題。

從語法上講,Synchronized總共有三種用法:

(1)修飾普通方法

(2)修飾靜態方法

(3)修飾代碼塊

  接下來我就通過幾個例子程序來說明一下這三種使用方式(為了便於比較,三段代碼除了Synchronized的使用方式不同以外,其他基本保持一致)。

1、沒有同步的情況:

代碼段一:

 1 package cn.com.jdk.thread;
 2 
 3 public class SynchronizedTest1 {
 4     public void method1(){
 5         System.out.println("Method 1 start");
 6         try {
 7             System.out.println("Method 1 execute");
 8             Thread.sleep(3000);
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12         System.out.println("Method 1 end");
13     }
14 
15     public void method2(){
16         System.out.println("Method 2 start");
17         try {
18             System.out.println("Method 2 execute");
19             Thread.sleep(1000);
20         } catch (InterruptedException e) {
21             e.printStackTrace();
22         }
23         System.out.println("Method 2 end");
24     }
25 
26     public static void main(String[] args) {
27         final SynchronizedTest1 test = new SynchronizedTest1();
28 
29         new Thread(new Runnable() {
30             @Override
31             public void run() {
32                 test.method1();
33             }
34         }).start();
35 
36         new Thread(new Runnable() {
37             @Override
38             public void run() {
39                 test.method2();
40             }
41         }).start();
42     }
43 }

執行結果如下:

1 Method 1 start
2 Method 1 execute
3 Method 2 start
4 Method 2 execute
5 Method 2 end
6 Method 1 end

線程1和線程2同時進入執行狀態,線程2執行速度比線程1快,所以線程2先執行完成,這個過程中線程1和線程2是同時執行的。

2、對普通方法同步:

代碼段二:

 1 package cn.com.jdk.thread;
 2 
 3 public class SynchronizedTest2 {
 4     public synchronized void method1(){
 5         System.out.println("Method 1 start");
 6         try {
 7             System.out.println("Method 1 execute");
 8             Thread.sleep(3000);
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12         System.out.println("Method 1 end");
13     }
14 
15     public synchronized void method2(){
16         System.out.println("Method 2 start");
17         try {
18             System.out.println("Method 2 execute");
19             Thread.sleep(1000);
20         } catch (InterruptedException e) {
21             e.printStackTrace();
22         }
23         System.out.println("Method 2 end");
24     }
25 
26     public static void main(String[] args) {
27         final SynchronizedTest2 test = new SynchronizedTest2();
28 
29         new Thread(new Runnable() {
30             @Override
31             public void run() {
32                 test.method1();
33             }
34         }).start();
35 
36         new Thread(new Runnable() {
37             @Override
38             public void run() {
39                 test.method2();
40             }
41         }).start();
42     }
43 }

執行結果如下:

1 Method 1 start
2 Method 1 execute
3 Method 1 end
4 Method 2 start
5 Method 2 execute
6 Method 2 end

跟代碼段一比較,可以很明顯的看出,線程2需要等待線程1的method1執行完成才能開始執行method2方法(也有可能先執行線程2,線程1需要等待線程2的method2執行完成才能開始執行method1方法),方法級別串行執行。

3、靜態方法(類)同步

代碼段三:

 1 package cn.com.jdk.thread;
 2 
 3 public class SynchronizedTest3 {
 4     public static synchronized void method1(){
 5         System.out.println("Method 1 start");
 6         try {
 7             System.out.println("Method 1 execute");
 8             Thread.sleep(3000);
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12         System.out.println("Method 1 end");
13     }
14 
15     public static synchronized void method2(){
16         System.out.println("Method 2 start");
17         try {
18             System.out.println("Method 2 execute");
19             Thread.sleep(1000);
20         } catch (InterruptedException e) {
21             e.printStackTrace();
22         }
23         System.out.println("Method 2 end");
24     }
25 
26     public static void main(String[] args) {
27         final SynchronizedTest3 test = new SynchronizedTest3();
28         final SynchronizedTest3 test2 = new SynchronizedTest3();
29 
30         new Thread(new Runnable() {
31             @Override
32             public void run() {
33                 test.method1();
34             }
35         }).start();
36 
37         new Thread(new Runnable() {
38             @Override
39             public void run() {
40                 test2.method2();
41             }
42         }).start();
43     }
44 }

執行結果如下:

1 Method 1 start
2 Method 1 execute
3 Method 1 end
4 Method 2 start
5 Method 2 execute
6 Method 2 end

對靜態方法的同步本質上是對類的同步(靜態方法本質上是屬於類的方法,而不是對象上的方法),所以即使test和test2屬於不同的對象,但是它們都屬於SynchronizedTest類的實例,所以也只能順序的執行method1和method2,(也有可能是線程2先執行,順序執行method2和method1)不能並發執行。

4、代碼塊同步

代碼段四:

 1 package cn.com.jdk.thread;
 2 
 3 public class SynchronizedTest4 {
 4     public void method1(){
 5         System.out.println("Method 1 start");
 6         try {
 7             synchronized (this) {
 8                 System.out.println("Method 1 execute");
 9                 Thread.sleep(3000);
10             }
11         } catch (InterruptedException e) {
12             e.printStackTrace();
13         }
14         System.out.println("Method 1 end");
15     }
16 
17     public void method2(){
18         System.out.println("Method 2 start");
19         try {
20             synchronized (this) {
21                 System.out.println("Method 2 execute");
22                 Thread.sleep(1000);
23             }
24         } catch (InterruptedException e) {
25             e.printStackTrace();
26         }
27         System.out.println("Method 2 end");
28     }
29 
30     public static void main(String[] args) {
31         final SynchronizedTest4 test = new SynchronizedTest4();
32 
33         new Thread(new Runnable() {
34             @Override
35             public void run() {
36                 test.method1();
37             }
38         }).start();
39 
40         new Thread(new Runnable() {
41             @Override
42             public void run() {
43                 test.method2();
44             }
45         }).start();
46     }
47 }

執行結果如下:

1 Method 1 start
2 Method 2 start
3 Method 1 execute
4 Method 1 end
5 Method 2 execute
6 Method 2 end

雖然線程1和線程2都進入了對應的方法開始執行,但是線程2在進入同步塊之前,需要等待線程1中同步塊執行完成,代碼塊級別串行。

二、Synchronized 原理

實際上,JVM只區分兩種不同用法 1.修飾代碼塊 2.修飾方法。上SE8規范:http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.14

上圖中,紅框框中說明了,1.monitorenter+monitorexit(上圖中的onlyMe方法中同步代碼塊) 2.修飾方法

  如果對上面的執行結果還有疑問,也先不用急,我們先來了解Synchronized的原理,再回頭上面的問題就一目了然了。

1、同步代碼塊:

我們先通過反編譯下面的代碼來看看Synchronized是如何實現對代碼塊進行同步的:

1 public class SynchronizedDemo {
2     public void method (){
3         synchronized (this) {
4             System.out.println("method 1 start!!!!");
5         }
6     }
7 }

javac -encoding utf-8 SynchronizedDemo.java 編譯生成class 后,javap -c 反編譯一下,看指令: 

這里着重分析2個monitorenter、monitorexit這兩個指令。這里以JSE8位為准,查到屬於JVM指令集。官網各種API、JVM規范,指令等,傳送門:http://docs.oracle.com/javase/8/docs/。

1.1,monitorenter監視器准入指令

關於這兩條指令的作用,我們直接參考JVM規范中描述:

這段話的大概意思為:

每個對象有一個監視器鎖(monitor)。當monitor被占用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:

1、如果monitor的進入數為0,則該線程進入monitor,然后將進入數設置為1,該線程即為monitor的所有者。

2、如果線程已經占有該monitor,只是重新進入,則進入monitor的進入數加1.

3.如果其他線程已經占用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權。

1.2,monitorexit監視器釋放指令

這段話的大概意思為:

1,執行monitorexit的線程必須是objectref所對應的monitor的所有者。

2,指令執行時,monitor的進入數減1,如果減1后進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權。 

  通過這兩段描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是為什么只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。

2、同步方法

我們再來看一下同步方法的反編譯結果:

源代碼:

1 package cn.com.jdk.thread;
2 
3 public class SynchronizedDemo0 {
4     public synchronized void method (){
5         System.out.println("method start!!!!");
6     }
7 }

反編譯結果:

  從反編譯的結果來看,方法的同步並沒有通過指令monitorenter和monitorexit來完成(理論上其實也可以通過這兩條指令來實現),不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之后才能執行方法體,方法執行完后再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需通過字節碼來完成。

三、運行結果解釋

  有了對Synchronized原理的認識,再來看上面的程序就可以迎刃而解了。

1、代碼段2結果:

  雖然method1和method2是不同的方法,但是這兩個方法都進行了同步,並且是通過同一個對象去調用的,所以調用之前都需要先去競爭同一個對象上的鎖(monitor),也就只能互斥的獲取到鎖,因此,method1和method2只能順序的執行。

2、代碼段3結果:

  雖然test和test2屬於不同對象,但是test和test2屬於同一個類的不同實例,由於method1和method2都屬於靜態同步方法,所以調用的時候需要獲取同一個類上monitor(每個類只對應一個class對象),所以也只能順序的執行。

3、代碼段4結果:

  對於代碼塊的同步實質上需要獲取Synchronized關鍵字后面括號中對象的monitor,由於這段代碼中括號的內容都是this,而method1和method2又是通過同一的對象去調用的,所以進入同步塊之前需要去競爭同一個對象上的鎖,因此只能順序執行同步塊。

四 、總結

  Synchronized是Java並發編程中最常用的用於保證線程安全的方式,其使用相對也比較簡單。但是如果能夠深入了解其原理,對監視器鎖等底層知識有所了解,一方面可以幫助我們正確的使用Synchronized關鍵字,另一方面也能夠幫助我們更好的理解並發編程機制,有助我們在不同的情況下選擇更優的並發策略來完成任務。對平時遇到的各種並發問題,也能夠從容的應對。

 全文參考:https://www.cnblogs.com/paddix/p/5367116.html 

                   https://www.cnblogs.com/dennyzhangdd/p/6670307.html#_labelTop

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM