synchronized與鎖公升級

2021-10-25 06:57:47 字數 2251 閱讀 5653

當乙個共享資源有可能被多個執行緒同時訪問並修改的時候,需要用鎖來保證資料的正確性。請看下圖:

執行緒a和執行緒b分別往同乙個銀行賬戶裡面新增貨幣,a執行緒從記憶體中讀取(read)當前賬戶金額($=0)到執行緒a的本地棧,進行+100的操作後,這時b執行緒也從記憶體中讀取當前金額($=0)到執行緒b的本地棧,並且進行+200的操作後寫回主存,執行緒b前腳剛寫回之後,後腳執行緒a又把$=100寫會到本地記憶體中。我們順便來複習一下jmm記憶體模型的8個原子操作

我們知道,volatile關鍵字只能保證變數的有序性可見性,但是不能保證原子性。在這個例子中,即使給$變數加上volatile關鍵字也是不頂用的,原因可見volatile為什麼不能保證原子性以及知乎提問:volatile為什麼不能保證原子性。

這時候就輪到我們的synchronized關鍵字出場了,它可以在訪問競態資源時加鎖,從而保證修改的時候不會出錯。它有三種作用範圍:

在jdk6以前,synchronized還屬於重量級鎖,每次枷鎖都依賴作業系統mutex lock實現,涉及到作業系統讓執行緒從使用者態切換到核心態,切換成本很高。在jdk6以後,研究人員引入了偏向鎖和輕量級鎖,因為sun公司的程式設計師發現大部分程式大多數時間都不會發生多個執行緒同時訪問競態資源的情況,每次執行緒都加鎖解鎖,每次這麼搞都要作業系統在使用者態和核心態之前來回切,太耗效能了。

首先要了解synchronized的實現原理,需要理解二個預備知識:

//下圖詳細介紹重要變數的作用

objectmonitor()

物件關聯的 objectmonitor 物件有乙個執行緒內部競爭鎖的機制,如下圖所示:

下面我們就來分析一下jdk6之前的synchronized具體的實現邏輯。

1.當有二個執行緒a、執行緒b都要開始給賬戶的經濟money變數加錢,要進行操作的時候 ,發現方法上加了synchronized鎖,這時執行緒排程到a執行緒執行,a執行緒就搶先拿到了鎖。拿到鎖的步驟為:

2.jvm 每次從waiting queue 的尾部取出乙個執行緒放到ondeck作為候選者,但是如果併發比較高,waiting queue會被大量執行緒執行cas操作,為了降低對尾部元素的競爭,將waiting queue拆分成contentionlist 和 entrylist 二個佇列, jvm將一部分執行緒移到entrylist 作為準備進ondeck的預備執行緒。另外說明幾點:

3.作為owner的a執行緒執行過程中,可能呼叫wait釋放鎖,這個時候a執行緒進入 wait set , 等待被喚醒。

以上就是synchronized在 jdk 6之前的實現原理。那麼jdk6對synchronized做了哪些優化呢?

3.2.1 加鎖

3.2.2 解鎖

輕量級鎖的解鎖過程也是通過cas操作來進行的,如果物件的mark word仍然指向執行緒的鎖記錄,那就用cas操作把物件當前的mark word和執行緒中複製的displaced mark word替換回來。假如能夠成功替換,那整個同步過程就順利完成了;如果替換失敗,則說明有其他執行緒嘗試過獲取該鎖(膨脹為重量級鎖,mark word指向了互斥量),就要在釋放重量級鎖的同時,喚醒被掛起的執行緒

3.2.3 使用條件

輕量級鎖能提公升程式同步效能的依據是「對於絕大部分鎖,在同步週期內都是不存在競爭的」這一經驗法則。如果沒有競爭,輕量級鎖通過cas操作成功避免了使用互斥量的開銷;但如果確實存在競爭,除了互斥量本身的開銷之外,還額外發生了cas操作的開銷。因此在有競爭的情況下,輕量級鎖反而會比傳統的重量級鎖更慢。

整個鎖公升級的過程如下圖所示:

Lock鎖與synchronized鎖的區別

1 synchronized鎖是可以幫助我們自動開鎖和關閉鎖 2 lock鎖,我們最常用的是reentrantlock重入鎖,需要我們手動的開鎖和手動關鎖 3 synchronized只能與wait notify 方法一起使用 4 reentrantlock只能與condition類中的await ...

synchronized物件鎖與類鎖

以前以為這兩個鎖是乙個鎖,最近測試了一下發現並不是這樣 如下 public class cusandpro public static synchronized void test2 catch interruptedexception e public static void main strin...

執行緒鎖 synchronized

使用 synchronized解決執行緒同步問題相比較nslock要簡單一些,日常開發中也更推薦使用此方法。首先選擇乙個物件作為同步物件 一般使用self 然後將 加鎖 爭奪資源的讀取 修改 放到 塊中。synchronized中的 執行時先檢查同步物件是否被另乙個執行緒占用,如果占用該執行緒就會處...