Java併發編程藝術---java併發編程機制的底層實現原理

2.1volatile的應用
volatile是輕量級的synchronized,它在多處理器開發中保證了變量的“可見性”。由於使用volatile不會引起線程的上下文切換,所以如果使用得當,會比synchronized的使用和執行成本更低。
2.1.1volatile的定義與實現原理
對volatile修飾的變量進行反編譯的時候,可以看到會有一個lock前綴的指令,這個指令在多核處理器下回做如下兩件事情:
(1)將當前處理器緩存行的數據寫回到系統內存。
(2)這個寫回內存的操作會使在其他CPU裏緩存了該內存地址的數據無效。
2.2synchronized實現原理與應用
synchronized的具體表現形式:
(1)對於普通方法,鎖是當前實例對象。
(2)對於靜態方法,鎖是當前類的Class對象。
(3)對於同步塊,鎖是synchronized括號裏配置的對象。
JVM在進入和退出Monitor對象來實現方法同步和代碼塊同步,但是兩者的實現方式是不一樣的。
代碼塊同步的使用monitorenter和monitorexit指令實現的,方法同步使用的是ACC_SYNCHRONIZED標誌來實現的。
任何一個對象都有一個montor與之關聯,當一個monitor被持有後,它將處於鎖定狀態。線程執行到monitorenter指令時,會嘗試獲取monitor對象的所有權。
2.2.1Java對象頭
synchronized使用的鎖是存在java對象頭裏的。java對象頭裏的Mark Word裏默認存儲對象的HashCode、分代年齡和鎖標記位。
2.2.2鎖的升級和對比
鎖的4中狀態:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態。
鎖的狀態只能升級不能降級。
(1)偏向鎖
在大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,爲了讓線程獲取鎖的代價更低而引入了偏向鎖。
當一個線程訪問同步塊並獲取鎖的時候,會在對象頭和棧幀中的鎖記錄記錄裏存儲偏向鎖的線程ID,以後這個線程再次進入和退出同步塊的時候不需要進行CAS操作來進行加鎖和解鎖,只需要簡單的測試一下對象頭的Mark Word裏是否存儲着指向當前線程的偏向鎖。如測試成功,表示線程已經獲取到了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成了1(表示當前是偏向鎖);如果沒有設置爲1,就是要CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。
1、偏向鎖的撤銷
當其他線程競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖。
偏向鎖的撤銷需要等到全局安全點,流程是:(1)暫停擁有偏向鎖的線程,然後檢查持有偏向鎖的線程是否活着,如線程不活,則將對象頭設置成無鎖狀態;如線程活着,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要麼重新偏向於其他線程,要麼恢復到無鎖活或者標記對象不合適作爲偏向鎖,最後喚醒暫停的線程。
(2)輕量級鎖
1、輕量級鎖加鎖
線程在進入同步代碼塊之前,JVM會先在當前線程的棧幀找中創建用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中。然後線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針。如成功,當前線程獲取到鎖,如失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
2、輕量級鎖解鎖
使用CAS操作將Mark Word替換到對象頭,成功,則表示沒有競爭發生,失敗,表示當前鎖存在競爭,鎖就會升級成重量級鎖。
爲什麼鎖升級之後,就不能再降級了?
因爲自旋會消耗CPU,爲了避免無用的自旋(比如獲得鎖的線程被阻塞了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。
2.3原子操作的實現原理
1、處理器如何實現原子操作
基於總線加鎖或者緩存加鎖的方式來實現多處理器之間的原子操作。
2、兩種情況下,處理器不會使用緩存鎖定
(1) 當操作的數據不能被緩存在處理器內部,或者操作的數據跨多個緩存行的時候,則處理器會調用總線鎖定。
(2)有些處理器不支持緩存鎖定。
以上的兩種情況,可以使用Intel處理器提供的Lock前綴指令來實現。
3、Java中如何實習原子操作
(1)使用循環CAS實現原子操作
JVM中的CAS操作正式利用了處理器提供的CMPXCHG指令實現。
從Java1.5開始,jdk的併發包裏面提供了一些類來支持原子操作,如AtomicBoolean等
(2)CAS操作存在的問題
(1)ABA問題。
(2)循環時間長,開銷大。
(3)只能保證一個共享變量的原子操作。
(3)使用鎖機制實現原子操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章