1. 程式人生 > 實用技巧 >SYNCHRONIZED的底層實現原理

SYNCHRONIZED的底層實現原理

如果對上面的執行結果還有疑問,也先不用急,我們先來了解Synchronized的原理,再回頭上面的問題就一目瞭然了。我們先通過反編譯下面的程式碼來看看Synchronized是如何實現對程式碼塊進行同步的:

1 package com.paddx.test.concurrent;
2 
3 public class SynchronizedDemo {
4     public void method() {
5         synchronized (this) {
6             System.out.println("Method 1 start");
7         }
8     }
9 }

反編譯結果:

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

monitorenter :

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

這段話的大概意思為:

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

1、如果monitor的進入數為0,則該執行緒進入monitor,然後將進入數設定為1,該執行緒即為monitor的所有者。

2、如果執行緒已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.

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

monitorexit: 

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

這段話的大概意思為:

執行monitorexit的執行緒必須是objectref所對應的monitor的所有者。

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

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

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

原始碼:

1 package com.paddx.test.concurrent;
2 
3 public class SynchronizedMethod {
4     public synchronized void method() {
5         System.out.println("Hello World!");
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關鍵字,另一方面也能夠幫助我們更好的理解併發程式設計機制,有助我們在不同的情況下選擇更優的併發策略來完成任務。對平時遇到的各種併發問題,也能夠從容的應對。