1. 程式人生 > >深入理解synchronized關鍵字

深入理解synchronized關鍵字

一、什麼是synchronized

簡單解釋:synchronized提供一種排他機制,也就是同一時間只能有一個執行緒執行某些操作。
權威解釋:synchronized關鍵字可以實現一個簡單的策略來防止執行緒干擾和記憶體一致性錯誤,如果一個物件對多個執行緒是可見的,那麼該物件的所有讀寫都將通過同步的方式來進行。
具體表現為:

  • synchronized關鍵字提供一種鎖機制,能夠確保變數的互斥訪問,從而防止資料不一致問題的出現。
  • synchronized關鍵字包括monitorenter和monitorexit兩個JVM指令,他能保證在任何時候任何執行緒執行到monitorenter成功之前都必須從主記憶體獲取資料,而不是從快取中,在monitorexit執行成功之後,共享變數被更新的值必須刷入主記憶體
  • 一個monitorexit指令之前必須有一個monitorenter。

二、synchronized關鍵字的用法

synchronized關鍵字可以修飾方法和程式碼塊,不能修飾class
同步方法:

 [public|private|protected] synchronized [static] type method()

同步程式碼塊:

private Object object = new Object();
public void sync(){
	synchronized(object){
	...
	}
}

三、JVM指令分析

synchronized關鍵字提供一種互斥機制,本質是某執行緒獲取了與MUTEX關聯的monitor鎖。使用JDK命令javap對有同步程式碼塊的類進行反彙編,輸出大量的JVM指令,在這些指令中有monitor enter和monitor exit成對出現。
這裡寫圖片描述


1、monitorenter
每個物件都與一個monitor相關聯,在一個執行緒嘗試獲得與物件關聯的monitor的所有權時發生如下幾件事:

  • 如果monitor的計數器為0,意味著該monitor的lock還沒有被獲得,某個執行緒獲得之後立即對計數器加一。
  • 如果一個已經擁有該monitor的執行緒重入,monitor計數器繼續累加。
  • 如果monitor已經被某執行緒獲得,其他執行緒嘗試獲得時會進去阻塞狀態,直到計數器變為0,才會再次嘗試獲得該monitor的所有權。

2、monitorexit
釋放monitor所有權就是將monitor計數器減一,直到計數器為0,意味著該執行緒不再擁有該monitor所有權,與此同時被該monitor阻塞的執行緒可再次嘗試獲得對該monitor的所有權。

四、使用synchroniezd關鍵字需要注意的問題

1、與monitor關聯的物件不能為空
2、synchronized作用域不應太大
由於synchronized關鍵字執行緒排他性,所有執行緒必須序列經過synchronized包裹的程式碼塊,如果synchronized作用域太大,則效率越低。
3、不同monitor企圖鎖相同方法
示例:

public class Task implements Runnable {
    private final Object OBJECT = new Object();
    @Override
    public void run() {
        //...
        synchronized (OBJECT){
            //...
        }
        //...
    }
}
public class Test {
    public static void main(String[] args) {
        for (int i=0;i<5;i++){
            new Thread(Task::new).start();
        }
    }
}

上面的程式碼構造了5個執行緒,同時也構造了5個Runnable例項。Runnable作為邏輯單元傳遞給Thread,每一個Runnable例項彼此獨立,也就是執行緒爭搶的monitor關聯引用彼此獨立,因此起不到互斥的作用。
4、多個鎖交叉導致死鎖
執行緒1獲得了A的鎖,等待B的鎖:執行緒2獲得了B的鎖,等待A的鎖。導致交叉死鎖。

五、程式死鎖的原因

1、交叉鎖
執行緒1獲得了A的鎖,等待B的鎖:執行緒2獲得了B的鎖,等待A的鎖。導致交叉死鎖。
2、記憶體不足
兩個執行緒T1和T2,執行某個任務時,T1已經獲得10M記憶體,T2已經獲得20M記憶體,每個執行緒執行單元都需要30M記憶體,但剩餘可用記憶體不足,那麼可能兩個執行緒都在等待彼此釋放資源記憶體,造成死鎖。
3、一問一答式的資料交換
服務端開啟某個埠等待客戶端訪問,客戶端傳送請求立即等待接收,但是由於某種原因服務端沒有接收到請求,此時服務端和客戶端都在等待對方傳送資料。造成死鎖。
4、資料庫鎖
比如某個執行緒執行了for update語句退出了事務,其他執行緒訪問資料庫時將陷入死鎖。
5、檔案鎖
某個執行緒獲得檔案鎖時意外退出,其他試圖讀取該檔案的執行緒將陷入死鎖,直到檔案控制代碼資源被釋放。
6、死迴圈引起的死鎖
程式由於程式碼原因陷入死迴圈,檢視執行緒堆疊資訊不會發現任何死鎖跡象,但程式不工作,CPU佔有率居高不下,這種死鎖一般稱作系統假死。