Java併發編程的藝術筆記
- 併發編程的挑戰
- Java併發機制的底層實現原理
- Java內存模型
- Java併發編程基礎
- Java中的鎖的使用和實現介紹
- Java併發容器和框架
- Java中的12個原子操作類介紹
- Java中的併發工具類
- Java中的線程池 Executor框架
Java代碼 編譯之後 得到 Java字節碼,被 類加載器加載到JVM中,最終 轉化爲彙編指令。
volatile
volatile
是輕量級的synchronized
,被volatile
修飾的變量,在一個線程能讀到這個變量被另一個線程修改之後的值。
volatile
不會引起線程上下文切換和調度。
volatile的兩條實現原則
- Lock前綴指令會引起處理器緩存回寫到內存.
- 一個處理器的緩存回寫到內存會導致其他處理器的緩存無效.
synchronized實現同步
- 對於 普通同步方法,鎖是 當前實例對象。
- 對於 靜態同步方法,鎖是 當前類的Class對象。
- 對於 同步方法塊,鎖是 Synchonized括號裏配置的對象。
Synchonized在JVM裏的實現原理
JVM 基於進入和退出Monitor
對象來實現方法同步和代碼塊同步。
- 代碼塊同步是使用
monitorenter
和monitorexit
指令實現的 - 方法同步是使用另外一種方式實現的,細節在JVM規範裏並沒有詳細說明。但是,方法的同步同樣可以使用
monitorenter
和monitorexit
指令來實現。
monitorenter
指令是在編譯後插入到同步代碼塊的開始位置,monitorexit
是插入到方法結束處和異常處。
JVM要保證每個monitorenter
必須有對應的monitorexit
與之配對。
任何對象都有一個monitor
與之關聯,當且一個monitor
被持有後,它將處於鎖定狀態。線程執行到monitorenter
指令時,將會嘗試獲取對象所對應的monitor
的所有權,即嘗試獲得對象的鎖。
Java對象頭
synchronized
用的鎖是存在Java對象頭裏的。在32位
虛擬機中,1字
寬 等於4字節
,即32bit
。
- 數組類型,虛擬機用
3個字寬
存儲對象頭。 - 非數組類型,虛擬機用
2個字寬
存儲對象頭。
鎖的4種狀態
級別從低到高依次是:
- 無鎖狀態
- 偏向鎖狀態
- 輕量級鎖狀態
- 重量級鎖狀態
原子操作的實現原理
原子(atomic
)本意是“不能被進一步分割的最小粒子”,而原子操作(atomic operation
)意爲“不可被中斷的一個或一系列操作”。
-
處理器如何實現原子操作
- 使用總線鎖保證原子性:所謂總線鎖就是使用處理器提供的一個
LOCK#
信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那麼該處理器可以獨佔共享內存。
- 使用總線鎖保證原子性:所謂總線鎖就是使用處理器提供的一個
-
使用緩存鎖保證原子性。
以下兩種情況不會使用緩存鎖:- 當處理器不支持緩存鎖定。
- 當操作的數據不能被緩存在處理器內部,或操作的數據跨多個緩存行時,則處理器會調用總線鎖定。
-
Java如何實現原子操作
- 使用循環CAS實現原子操作, Java中的12個原子操作類介紹。
-
CAS實現原子操作的三大問題:
- ABA問題:因爲CAS需要在操作值的時候,檢查值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。
ABA
問題的解決思路就是 使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加1,那麼A→B→A
就會變成1A→2B→3A
。
原子操作類AtomicStampedReference
的compareAndSet
方法的作用是首先檢查當前引用是否等於預期引用,並且檢查當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。 - 循環時間長開銷大:自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。
- 只能保證一個共享變量的原子操作:當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖。還有一個取巧的辦法,就是把多個共享變量合併成一個共享變量來操作。
- ABA問題:因爲CAS需要在操作值的時候,檢查值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。
-
使用鎖機制實現原子操作
- 鎖機制保證了只有獲得鎖的線程才能夠操作鎖定的內存區域。
如果覺得不錯的話,請幫忙點個讚唄。