Java並發——Synchronized及其實現原理
Synchronized
是Java中實現進程同步最簡單的一種方式,Synchronized
主要有以下三個作用:
- 保證互斥性
- 保證可見性
- 保證順序性
接下來我們就來看下Synchronized的實現原理,看看Synchronized是如何實現上述三個作用的。
Synchronized
有三種常用方法:
- 修飾普通方法
- 修飾靜態方法
- 修飾代碼塊
具體的用法這裏就不展開了,不熟悉的朋友建議先試用一下。
1 Synchronized
實現原理
先來看下下麵這段代碼:
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
這是一個典型的Synchronized
修飾代碼塊的例子,Synchronized
的對象是實例對象。意味著對一個SynchronizedDemo對象,同一時間內隻能有一個線程可以執行method方法。
接下來我們來看看上述代碼對應的字節碼:
可以看到synchronized
所覆蓋的代碼塊的首尾部分分別加上了monitorenter
和monitorexit
。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
- 如果當前monitor的進入數為0,則說明monitor未被占用,則當前線程占有該monitor,同時monitor的進入數變為1
- 如果當前monitor進入數不為0,且monitor的占用者是當前線程,則可以重複占用該monitor,同時進入數加1
- 如果其他線程占有了該monitor,則線程進入阻塞狀態,直到monitor的進入數重新變為0,再重新嚐試獲取所有權
-
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來實現的。在前文的代碼demo中,關聯的對象實際就是SynchonizedDemo
實例對象的monitor。其實wait/notify等方法也依賴於monitor對象,這就是為什麼隻有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。
接下來再來看看Synchronized
修飾方法的場景:
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
對應的字節碼為:
從反編譯的結果來看,方法的同步並沒有通過指令monitorenter和monitorexit來完成(理論上其實也可以通過這兩條指令來實現),不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質上沒有區別,隻是方法的同步是一種隱式的方式來實現,無需通過字節碼來完成。
當Synchronized修飾不同方法時,獲取的monitor對象不同:
- 修飾對象方法:此時獲取的monitor為實例對象(即this)的monitor
- 修飾類方法:此時獲取的monitor為類class對象(如Synchronized.class)的monitor
通過上述解釋,相信大家已經知道Synchronized
是如何保證互斥性的,那麼其是怎麼實現可見性和順序性的呢?其實也跟monitor相關:
- 可見性:執行到monitorenter時,線程會重新從主內存中將數據同步到本地工作內存,從而保證其可以看到其他線程的修改。同時執行monitorexit時,線程也會將本地工作內存的數據同步到主內存中
- 有序性:monitorenter、monitorexit修飾的代碼將禁止進行重排序
最後更新:2017-07-26 09:04:23