synchronized 原理

zhusir發表於2018-08-27

更多請看:synchronized 原理

java當中的synchronized用的是悲觀鎖

使用方式及作用物件

  • 同步普通方法,鎖的是當前物件。
  • 同步靜態方法,鎖的是當前 Class 物件。
  • 同步塊,鎖的是 () 中的物件。

實現原理

JVM 是通過進入、退出物件監視器( Monitor )來實現對方法、同步塊的同步的。
具體實現是在編譯之後在同步方法呼叫前加入一個 monitor.enter 指令,在退出方法和異常處插入 monitor.exit 的指令。
其本質就是對一個物件監視器( Monitor )進行獲取,而這個獲取過程具有排他性從而達到了同一時刻只能一個執行緒訪問的目的。
而對於沒有獲取到鎖的執行緒將會阻塞到方法入口處,直到獲取鎖的執行緒 monitor.exit 之後才能嘗試繼續獲取鎖。

鎖優化

JDK1.6 中對 synchronized 進行了各種優化,為了能減少獲取和釋放鎖帶來的消耗引入了偏向鎖輕量鎖

輕量鎖

當程式碼進入同步塊時,如果同步物件為無鎖狀態時,當前執行緒會在棧幀中建立一個鎖記錄(Lock Record)區域,同時將鎖物件的物件頭中 Mark Word 拷貝到鎖記錄中,再嘗試使用 CASMark Word 更新為指向鎖記錄的指標。
如果更新成功,當前執行緒就獲得了鎖。
如果更新失敗 JVM 會先檢查鎖物件的 Mark Word 是否指向當前執行緒的鎖記錄。
如果是則說明當前執行緒擁有鎖物件的鎖,可以直接進入同步塊。
不是則說明有其他執行緒搶佔了鎖

如果存在多個執行緒同時競爭一把鎖,輕量鎖就會膨脹為重量鎖

偏向鎖

偏向鎖的特徵是:鎖不存在多執行緒競爭,並且應由一個執行緒多次獲得鎖。


適應性自旋

在使用 CAS 時,如果操作失敗,CAS 會自旋再次嘗試。由於自旋是需要消耗 CPU 資源的,所以如果長期自旋就白白浪費了 CPU。JDK1.6以後對自旋進行了優化:如果某個鎖自旋很少成功獲得,那麼下一次就會減少自旋。

CAS

樂觀鎖的核心演算法是CAS(campare and swap 比較並交換)

實現原理

首先檢查某塊記憶體的值是否跟之前我讀取時的一樣,如不一樣則表示期間此記憶體值已經被別的執行緒更改過,捨棄本次操作,否則說明期間沒有其他執行緒對此記憶體值操作,可以把新值設定給此塊記憶體。

CAS是原子性的,這個原子性是由硬體級別的指令來保證的。

ABA問題

假如記憶體值原來是A,後來被一條執行緒改為B,最後又被改成了A,則CAS認為此記憶體值並沒有發生改變,但實際上是有被其他執行緒改過的,這種情況對依賴過程值的情景的運算結果影響很大,解決的思路是引入版本號,每次變數更新都把版本號加一。

悲觀鎖和樂觀鎖

悲觀鎖

總是假設最壞的情況,認為每次取資料時資料都會被其他執行緒所修改,所以都會加鎖(行鎖,表鎖等),當其他執行緒想要讀取資料時,都需要阻塞掛起。可以依靠資料庫實現,如行鎖、讀鎖和寫鎖等。(synchronized的思想也是悲觀鎖)

樂觀鎖

總是認為不會有併發問題,認為每次取資料時資料不會被其他執行緒更改,所以不會加鎖,但是在更新之前會判斷資料有沒有被其他執行緒修改,一般通過版本號或CAS操作實現

總之:讀取頻繁使用樂觀鎖,寫入頻繁使用悲觀鎖。


相關文章