您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java并發機制底層實現原理是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java并發機制底層實現原理是什么”吧!
Java并發機制的底層實現原理
術語了解
緩存行:緩存的最小操作單位。
原子操作:不可被中斷的一個或一系列操作。
CAS(Compare and Swap),比較并設置。用于在硬件層面上提供原子性操作。CAS操作需要輸入二個數值,一個舊值(進行操作前的值)和一個新值,在操作期間比較舊值是否發生變化,沒有發生變才替換為新值,發生了變化則不進行交換。
volatile是輕量級的synchronized,在多處理器開發中它保證了變量的可見性.即當一個線程修改共享變量時,另外一個線程能讀到這個修改的值.
如果一個字段被聲明為volatile,Java線程內存模型確保所有線程看到這個變量的值是一致的.
volatile具有可見性,有序性,不具有原子性。
//Java代碼如下 instance = new Sington(); //instance 是volatile變量 //轉換為匯編代碼如下: 0x01a3deld: movb $0x0,ox1104800(%esi);0x01a3de24: lock add1 $0x0,(%esp);
有volatile變量修飾的共享變量進行寫操作時會多出第二行匯編代碼,查閱IA-32架構軟件開發者手冊會發現,Lock前綴指令在多核處理器下會引發二件事情:
將當前處理器緩存行的數據寫回到系統內存.
這個寫回內存的操作會使在其他CPU里緩存該內存地址的數據無效.
為了提高處理速度,處理器不直接和內存進行通信,而是先將系統內存的數據讀到內部緩存(L1,L2或其他)后再進行操作,但操作完不知道何時會寫到內存。如果對聲明了volatile的變量進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫回到系統內存。但是,就算寫回到內存,如果其他處理器緩存的值還是舊的,再執行計算操作就會有問題。所以,在多處理器下,為了保證各個處理器的緩存是一致的,就會實現緩存一致性協議,每個處理器通過嗅探在總線上傳播的數據來檢查自己緩存的值是不是過期了,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器對這個數據進行修改操作的時候,會重新從系統內存中把數據讀到處理器緩存里。
由1.1小節可知,volatile的實現規則即要達到上面的效果,具體二條規則為:
Lock前綴指令會引起處理器緩存回寫到內存.
一個處理器緩存回寫到內存會導致其他處理器的緩存無效.
synchronized具有有序性,可見性,原子性。
synchronized實現同步的基礎---Java中的每一個對象都可以作為鎖,具體表現為:
對于普通同步方法,鎖是當前實例對象.
對于靜態同步方法,鎖是當前Class對象.
對于同步方法塊,鎖是synchronized括號內配置的對象.
synchronized的鎖是存放在Java對象頭里面的.準確的說是對象頭里面的Mark Word里面.
如果對象是非數組類型,則用二個字寬存儲對象頭;如果對象是數組內型,則虛擬機用三個字寬存儲對象頭.(Java對象為一個數組時對象頭還必須有一塊用于記錄數組長度的數據。因為Java數組元數據中沒有數組大小的記錄)
Java對象頭里面有:
Mark Word.一個字寬,用于存儲對象的hashCode或鎖信息.
Class Metadata Address.一個字寬,存儲對象類型數據的指針.
Array Length.一個字寬,用于存儲數組的長度(前提對象是數組).
其中Mark Word的數據會隨鎖標志位的變化而變化
鎖一共有四中狀態,從低到高為:無鎖狀態,偏向鎖狀態,輕量級鎖狀態,重量級鎖狀態.這些狀態會隨著競爭而升級,但是鎖只能升級而不能降級.
大多數情況鎖不僅不存在多線程競爭,而且大多由同一線程多次獲取,為了降低獲取鎖的成本引入了偏向鎖.
偏向鎖是一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖的時候,持有偏向鎖的線程才會釋放鎖。
當一個線程獲取鎖成功時,會在對象頭的Mark Word及棧幀中的鎖記錄里存儲鎖偏向的線程ID.以后線程在進入和退出同步塊時不需要使用CAS進行加鎖和解鎖.
只有當其它線程競爭時,持有偏向鎖的線程才會釋放鎖.
偏向鎖的撤銷需要等待全局安全點.(在這個時間點上沒有正在執行的字節碼)
以上為偏向鎖的特點.
偏向鎖的獲取
檢查對象頭的Mark Word里是否存儲了當前線程的線程ID.是則表示線程已經獲得了鎖;不是則檢查Mark Word中的偏向鎖標識是否被設置為1.
若設置為1(表示當前鎖狀態是偏向鎖),則使用CAS將對象頭的偏向鎖指向當前線程.
若沒有設置為1(表示當前鎖狀態不是偏向鎖),則使用CAS競爭鎖;
偏向鎖的撤銷
首先暫停持有偏向鎖的線程,并判斷該線程是否活著,若線程不處于活動狀態,則將對象頭了的Mark Word設置為無鎖狀態.
若線程仍然活著,則將持有偏向鎖的棧中的鎖記錄及對象頭的Mark Word,要么偏向其它線程,要么恢復到無鎖狀態,要么標記對象不適合作為偏向鎖,最后喚醒暫停的線程。
關閉偏向鎖
通過JVM參數 -XX:BiasedLockingStartupDelay=0 來關閉延遲啟動.
通過JVM參數 -XX:-UseBiasedLocking=false關閉偏向鎖,關閉后默認進入輕量級鎖狀態.
輕量鎖加鎖
線程在執行同步塊前,JVM先在當前線程的棧幀中創建用于存儲鎖記錄的空間,并將對象頭的Mark Word復制到鎖記錄中.
線程嘗試將對象頭的Mark Word替換為指向棧中鎖記錄的指針,若成功,當前線程獲得鎖.
若失敗,表示其它線程在競爭鎖,當前線程便嘗試使用自旋來獲取鎖.
輕量鎖解鎖
使用原子的CAS操作將當前線程棧幀中的鎖記錄存儲的Mark Word替換回到對象頭.
若成功,表示沒有線程在競爭鎖.
若失敗,表示當前鎖存在競爭,鎖便會膨脹為重量級鎖.Mark Word中的鎖狀態變為指向重量級鎖的指針.
因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞了),鎖一旦升級為重量級鎖便不會恢復.當鎖處于重量級鎖狀態時,其它線程試圖訪問時度會被阻塞.
鎖 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
偏向鎖 | 加鎖和解鎖不需要額外的消耗,和執行非同步方法比僅存在納秒級的差距。 | 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 | 適用于只有一個線程訪問同步塊場景。 |
輕量級鎖 | 競爭的線程不會阻塞,提高了程序的響應速度。 | 如果始終得不到鎖競爭的線程使用自旋會消耗CPU | 追求響應時間。同步塊執行速度非常快。 |
重量級鎖 | 線程競爭不使用自旋,不會消耗CPU。 | 線程阻塞,響應時間緩慢。 | 追求吞吐量。同步塊執行速度較長。 |
總線鎖就是使用處理器提供的一個LOCK #信號,當一個處理器在總線上輸出此信號時,其它處理器的請求將會被阻塞住,那么該處理器可以獨占共享內存。但是在鎖定期間其它處理器不能操作其它內存地址的數據,所以總線鎖定開銷較大。
使用CAS實現的線程安全的計數器代碼。CAS實現原子操作的問題:
ABA問題
CAS是根據舊值有沒有發生變化來更新新值的,所以為了解決ABA問題,可以在變量前加入版本號A->B->A就變成了1A->2B->3A.
循環時長開銷大
只能保持一個共享變量的原子操作
jdk1.5后開始,提供了AtomicReference類來保證引用對象之間的原子性,就可以把多個變量放在一個對象里面進行CAS操作。
JVM內部實現了很多種鎖機制,偏向鎖,互斥鎖,輕量級鎖。
感謝各位的閱讀,以上就是“Java并發機制底層實現原理是什么”的內容了,經過本文的學習后,相信大家對Java并發機制底層實現原理是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。