[Java併發系列] 1.Java併發機制的底層實現

xiangdong發表於2017-11-11

在Java併發實現的機制中,大部分的容器和框架都是依賴於volatile/synchronized/原子操作實現的,瞭解底層的併發機制,對於併發程式設計會帶來很多幫助

一、 synchronized的應用

synchronized在多執行緒併發程式設計中已經是一個元老級的存在,通常被稱作是重量級鎖。既然是常用的一種鎖,那麼就需要對它的底層實現有深入的瞭解。

1. synchronized的實現原理

當一個執行緒在訪問同步程式碼塊時,就必須要先獲取該程式碼塊中物件的鎖,退出或者丟擲異常時,就必須要釋放鎖。synchronized的同步實現,是JVM基於進入和退出同步物件的Monitor物件來實現方法同步和程式碼塊同步的。
每個Monitor物件都有與之關聯的monitor,當且僅當monitor被持有後,它才會處於鎖定狀態。同步程式碼是使用monitorenter和monitorexit指令實現的。monitorenter指令時在程式碼編譯後插入到同步程式碼塊的開始位置,monitorexit指令則是插入到方法的結束和異常處。當執行緒執行到monitorenter時,會嘗試去獲取物件的monitor的所有權,即嘗試獲取物件的鎖。

2. synchronized的使用形式及意義
  • 修飾普通方法:鎖是當前例項物件
  • 修飾靜態方法:鎖是當前類的Class物件
  • 修飾程式碼塊:鎖是synchronized括號裡面配置的物件
3. 鎖的升級

鎖共有四種狀態:無鎖狀態->偏向鎖狀態->輕量級鎖狀態->重量級鎖狀態

偏向鎖:當執行緒訪問同步塊並獲取鎖時,會在物件頭和棧針中的鎖記錄中儲存鎖偏向的ID,以後該執行緒在進入和退出同步塊時就不需要在使用CAS操作來進行加鎖和解鎖操作,而僅僅需要測試一下物件頭中的Mark Word欄位中儲存的執行緒ID是否是當前執行緒即可。如果是,那麼表示獲取鎖成功;如果不是,則需要檢查物件頭中偏向鎖的欄位是否設定為了1(表示當前鎖是偏向鎖),如果沒有設定,則需要使用CAS來競爭獲取鎖,如果已經設定了,則嘗試使用CAS將物件頭的偏向鎖ID指向當前執行緒。
輕量級鎖;

  • 輕量級鎖加鎖:執行緒在執行同步塊之前,首先會在自己的執行緒棧中建立一個用於儲存鎖記錄的空間,並將物件中的Mark Word 賦值到鎖記錄中。然後執行緒嘗試使用CAS操作將物件頭中的Mark Word替換為指向鎖記錄的指標。如果成功,則表示當前執行緒獲取鎖,如果失敗,則表示其他執行緒也在競爭鎖,當前執行緒便使用自循的方式來獲取鎖。
  • 輕量級鎖解鎖:輕量級鎖解鎖時,會使用原子CAS操作將鎖記錄替換會物件頭,如果成功,則表示沒有競爭發生。如果失敗,則表示當前鎖存在競爭,鎖就會膨脹為重量級鎖。
4. 鎖的對比
鎖型別 優點 缺點 適用場景
重量級鎖 執行緒競爭不使用自旋,不會浪費CPU 執行緒阻塞,響應時間慢 追求吞吐量,同步塊執行的時間長的場景
輕量級鎖 競爭的執行緒不會阻塞,提高程式的響應速度 如果執行緒長時間得不到鎖,那麼自旋就會浪費CPU 追求響應時間,同步塊執行速度快
偏向鎖 加鎖和解鎖不需要額外的資源消耗 如果執行緒間存在競爭,會帶來額外的鎖撤銷的消耗 適用於只有一個執行緒訪問同步場景

二、volatile的應用

volatile是輕量級的synchronized,volatile在多執行緒開發中保證了共享變數的可見性,所謂可見性,指的是當一個執行緒修改了共享變數之後,對於其他執行緒來說,能夠讀到這個修改的值。

1. volatile的定義及實現原理

volatile定義:Java程式語言允許執行緒訪問共享變數,為了確保共享變數能被準確和一致地更新,執行緒應該確保通過排他鎖獲得這個變數。
實現原理:當對宣告瞭volatile的變數進行寫操作時,JVM就會向處理器傳送一條帶有lock字首的指令,將這個變數所在的快取行的資料寫回到系統記憶體中。同時,在多處理器的情況下,需要執行快取一致性協議,即每個處理器都需要通過嗅探匯流排上傳播的資料來檢查自己的快取是否過期,當處理器發現自己快取行對應的記憶體地址被修改,就會將當前處理器的快取行設定為無效,當處理器需要對這個資料進行操作時,再從系統記憶體中把資料讀取到快取行中。

2. 實現volatile的兩條原則
  • 帶有Lock字首的指令會引起處理器快取寫回到記憶體;
  • 一個處理器的快取寫回到記憶體,會導致其他處理器的快取無效。

三、原子操作的原理

見文章[併發程式設計系列]Java中的原子操作類

相關文章