天天看點

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)使用鎖機制實作原子操作