1. 程式人生 > >《Java併發程式設計的藝術》筆記二——Java併發機制的底層實現原理.md

《Java併發程式設計的藝術》筆記二——Java併發機制的底層實現原理.md

0.Java程式碼執行過程

Java程式碼在編譯之後會變成Java位元組碼,Java位元組碼被類載入器載入到JVM中,JVM執行位元組碼,最終轉化為彙編指令在CPU上執行,Java中所使用的併發機制依賴與JVM的實現和CPU的執行。 b本節探討下Java併發機制的實現原理。

1. volatile的應用

在併發程式設計中synchronized和volatile都扮演者重要角色。volatile是輕量級的synchronized,作用是在多執行緒中保證共享變數的“可見性”。可見性的意思是,當一個執行緒修改 一個共享變數時,另一個執行緒能讀到這個修改的值。 如果volatile變數修飾符使用恰當的話,他會比synchronized的使用執行成本更低,因為它不會執行緒的上下文切換和排程。

volatile 的定義與實現原理

Java語言規範第3版中的volatile 定義如下: Java語言允許執行緒訪問共享變數,為了保證共享變數能夠被準確一致的更新,執行緒應該確保通過排它鎖單獨獲得這個變數。如果一個欄位被宣告成volatile,Java執行緒記憶體模型確保所有執行緒看到這個變數的值是一致的。

volatile是如何來保證可見性的呢?

volatile instance = new Singleton();

這行程式碼在X86處理器下編譯生成的彙編指令中會多出一行彙編程式碼:lock addl $0x0,(%esp);

這行程式碼會在用volatile修飾共享變數時出現。Lock字首的指令在多核處理器下會引發兩件事:

1.將當前處理器快取行的資料寫回到系統記憶體。

2.這個寫回記憶體的操作,會使其他CPU中快取了該記憶體地址的資料無效。

為了提高處理速度,處理器不直接和記憶體進行通訊,而是先將系統記憶體的資料讀到內部快取中後,再進行操作。但是,操作完之後不知道何時寫回記憶體中。如果對聲明瞭volatile的變數進行寫操作,JVM就會向處理器發出一條帶有LOCK字首的指令,告訴CPU,將這個變數所在的快取行的資料寫回到系統記憶體。

所以總結下,volatile的兩條實現原則:

1)Lock字首執行會引起處理器將快取寫回到記憶體。

2)一個處理器的快取寫回到記憶體後,會導致其他處理器的快取無效。

2.synchronized的實現原理與應用

本節介紹JavaSE1.6 中為了減少獲得鎖和釋放鎖帶來的效能消耗而引入的偏向鎖和輕量級鎖,以及鎖的儲存結果和升級過程。

2.1synchronized實現同步的基礎:Java中的每一個物件都可以為鎖。

具體表現為以下3中形式:

  • 對於普通的同步方法,鎖是當前例項物件

  • 對於靜態同步方法,鎖是當前類的Class物件

  • 對於同步方法,鎖是Synchronized括號裡配置的物件。

1.當一個執行緒訪問同步程式碼塊時,首先它必須得到鎖,退出和丟擲異常時必須釋放鎖。

2.JVM通過進入和退出Monitor物件來實現方法同步和程式碼塊的同步,但是兩者的實現細節不一樣。程式碼塊的同步是使用monitorenter和monitorexit指令實現的,方法同步JVM規範中沒有詳細說明。

3.monitorenter指令是在編譯後插入同步程式碼塊的起始位置,而Monitorexit是插入在方法結束處和異常處。

4.JVM要保證每個monitorenter都有一個monitorexit與之對應,。任何一個物件都有一個monitor與之關聯,而且當一個monitor被持有後,他將處於鎖定狀態。當執行緒執行到monitorenter指令時,將會嘗試獲取物件對應的monitor的所有權,即嘗試獲得物件的鎖。

2.2物件頭

synchronized用的鎖是存在Java物件頭裡的。如果物件是非陣列型別,則用兩個字寬儲存物件頭,如果物件是陣列,則虛擬機器用3個字寬(Word)儲存物件頭,其中一個字寬儲存的是陣列的長度。

物件頭分為兩個部分:Mark Word和類元資料地址

  • Mark Word預設儲存物件自身的執行時資料,如雜湊碼,GC分代年齡,鎖標記位
  • 類元資料地址是型別指標,即物件指向他的類元資料的指標,虛擬機器通過這個指標來確定物件是哪個類的例項。如果物件是一個Java陣列,那在物件頭中還必須有一塊用於記錄陣列長度的資料。並不是所有的虛擬機器實現都必須在物件資料上保留型別指標,換句話說,查詢物件的元資料資訊並不一定要經過物件本身。