JVM 進行執行緒同步背後的原理
前言
所有的 Java 程式都會被翻譯為包含位元組碼的 class 檔案,位元組碼是 JVM 的機器語言。這篇文章將闡述 JVM 是如何處理執行緒同步以及相關的位元組碼。
執行緒和共享資料
Java 的一個優點就是在語言層面支援多執行緒,這種支援集中在協調多執行緒對資料的訪問上。
JVM 將執行時資料劃分為幾個區域:一個或多個棧,一個堆,一個方法區。
在 JVM 中,每個執行緒擁有一個棧,其他執行緒無法訪問,裡面的資料包括:區域性變數,函式引數,執行緒呼叫的方法的返回值。棧裡面的資料只包含原生資料型別和物件引用。在 JVM 中,不可能將實際物件的拷貝放入棧。所有物件都在堆裡面。
JVM 只有一個堆,所有執行緒都共享它。堆中只包含物件,把單獨的原生型別或者物件引用放入堆也是不可能的,除非它們是物件的一部分。陣列也在堆中,包括原生型別的陣列,因為在 Java 中,陣列也是物件。
除了棧和堆,另一個存放資料的區域就是方法區了,它包含程式中使用到的所有類(靜態)變數。方法區類似於棧,也只包含原生型別和物件引用,但是又跟棧不同,方法區中類變數是執行緒共享的。
物件鎖和類鎖
正如前面所說,JVM 中的兩個區域包含執行緒共享的資料,分別是:
- 堆:包含所有物件
- 方法區:包含所有類變數
如果多個執行緒需要同時使用同一個物件或者類變數,它們對資料的訪問必須被恰當地控制。否則,程式會產生不可預測的行為。
為了協調多個執行緒對共享資料的訪問,JVM 給每個物件和類關聯了一個鎖。鎖就像是任意時間點只有一個執行緒能夠擁有的特權。如果一個執行緒想要鎖住一個特定的物件或者類,它需要向 JVM 請求鎖。執行緒向 JVM 請求鎖之後,可能很快就拿到,或者過一會就拿到,也可能永遠拿不到。當執行緒不需要鎖之後,它把鎖還給 JVM。如果其他執行緒需要這個鎖,JVM 會交給該執行緒。
類鎖的實現其實跟物件鎖是一樣的。當 JVM 載入類檔案的時候,它會建立一個對應類java.lang.Class
物件。當你鎖住一個類的時候,你實際上是鎖住了這個類的Class
物件。
執行緒訪問物件例項或者類變數的時候不需要獲取鎖。但是如果一個執行緒獲取了一個鎖,其他執行緒不能訪問被鎖住的資料,直到擁有鎖的執行緒釋放它。
管程
JVM 使用鎖和管程協作。管程監視一段程式碼,保證一個時間點內只有一個執行緒能執行這段程式碼。
每個管程與一個物件引用關聯。當執行緒到達管程監視程式碼段的第一條指令時,執行緒必須獲取關聯物件的鎖。執行緒不能執行這段程式碼直到它得到了鎖。一旦它得到了鎖,執行緒可以進入被保護的程式碼段。
當執行緒離開被保護的程式碼塊,不管是如何離開的,它都會釋放關聯物件的鎖。
多次鎖定
一個執行緒被允許鎖定一個物件多次。對於每個物件,JVM 維護了一個鎖的計數器。沒有被鎖的物件計數為 0。當一個執行緒第一次獲取鎖,計數器自增變為 1。每次這個執行緒(已經得到鎖的執行緒)請求同一個物件的鎖,計數器都會自增。每次執行緒釋放鎖,計數器都會自減。當計數器變為 0 時,鎖才被釋放,可以給別的執行緒使用。
同步塊
在 Java 語言的術語中,協調多個執行緒訪問共享資料被稱為同步(synchronization)。Java 提供了兩種內建的方式來同步對資料的訪問:
- 同步語句
- 同步方法
同步語句
為了建立同步語句,你需要使用synchronized
關鍵字,括號裡面是同步的物件引用,如下所示:
class KitchenSync { private int[] intArray = new int[10]; void reverseOrder() { synchronized (this) { int halfWay = intArray.length / 2; for (int i = 0; i < halfWay; ++i) { int upperIndex = intArray.length - 1 - i; int save = intArray[upperIndex]; intArray[upperIndex] = intArray[i]; intArray[i] = save; } } } }
在上面的例子中,被同步塊包含的語句不會被執行,直到執行緒得到this
引用的物件鎖。如果不是鎖住this
引用,而是鎖住其他物件,線上程執行同步塊語句之前,它需要獲得該物件的鎖。
有兩個位元組碼monitorenter
和monitorexit
,被用來同步方法中的同步塊。
位元組碼 | 運算元 | 描述 |
---|---|---|
monitorenter | 無 | 取出物件引用,請求與物件引用關聯的鎖 |
monitorexit | 無 | 取出物件引用,釋放與物件引用關聯的鎖 |
當monitorenter
被 JVM 執行時,它請求棧頂物件引用關聯的鎖。如果該執行緒已經擁有該物件的鎖,計數器自增。每次monitorexit
被執行,計數器自減。當計數器變為 0 時,該鎖被釋放。
注意:當同步塊中丟擲異常時,catch
語句保證物件鎖被釋放。不管同步塊是如何退出的,JVM 保證執行緒會釋放鎖。
同步方法
為了同步整個方法,你只需要在方法宣告前面加上synchronized
關鍵字。
class HeatSync { private int[] intArray = new int[10]; synchronized void reverseOrder() { int halfWay = intArray.length / 2; for (int i = 0; i < halfWay; ++i) { int upperIndex = intArray.length - 1 - i; int save = intArray[upperIndex]; intArray[upperIndex] = intArray[i]; intArray[i] = save; } } }
JVM 不會使用特殊的位元組碼來呼叫同步方法。當 JVM 解析方法的符號引用時,它會判斷方法是不是同步的。如果是,JVM 要求執行緒在呼叫之前請求鎖。對於例項方法,JVM 要求得到該例項物件的鎖。對於類方法,JVM 要求得到類鎖。在同步方法完成之後,不管它是正常返回還是丟擲異常,鎖都會被釋放。
相關文章
- Java 執行緒同步原理探析Java執行緒
- 執行緒的同步執行緒
- JVM中的執行緒行為JVM執行緒
- 最全java多執行緒總結2--如何進行執行緒同步Java執行緒
- 深入理解JVM(③)執行緒與Java的執行緒JVM執行緒Java
- 多執行緒和多執行緒同步執行緒
- 【多執行緒總結(二)-執行緒安全與執行緒同步】執行緒
- 多執行緒Demo學習(執行緒的同步,簡單的執行緒通訊)執行緒
- 執行緒同步方法執行緒
- 理解執行緒同步執行緒
- 深入執行緒同步執行緒
- 執行緒與同步非同步執行緒非同步
- 多執行緒(2)-執行緒同步互斥鎖Mutex執行緒Mutex
- Java中的執行緒同步Java執行緒
- Java執行緒池二:執行緒池原理Java執行緒
- Java多執行緒學習(3)執行緒同步與執行緒通訊Java執行緒
- 非同步/同步,阻塞/非阻塞,單執行緒/多執行緒概念梳理非同步執行緒
- 進執行緒執行緒
- 多執行緒(2)-執行緒同步條件變數執行緒變數
- Java多執行緒之執行緒同步【synchronized、Lock、volatitle】Java執行緒synchronized
- C#多執行緒開發-執行緒同步 02C#執行緒
- java 多執行緒 –同步Java執行緒
- java 多執行緒 --同步Java執行緒
- 執行緒同步機制執行緒
- #大學#Java多執行緒學習02(執行緒同步)Java執行緒
- Java多執行緒—執行緒同步(單訊號量互斥)Java執行緒
- JAVA多執行緒詳解(3)執行緒同步和鎖Java執行緒
- SpringBoot執行緒池和Java執行緒池的實現原理Spring Boot執行緒Java
- Java併發(四)----執行緒執行原理Java執行緒
- java多執行緒:執行緒池原理、阻塞佇列Java執行緒佇列
- 走進Java Android 的執行緒世界(二)執行緒池JavaAndroid執行緒
- iOS執行緒、同步非同步、序列並行佇列iOS執行緒非同步並行佇列
- 執行緒池原理初探執行緒
- 執行緒啟動原理執行緒
- 【多執行緒】ThreadLocal原理執行緒thread
- java多執行緒原理Java執行緒
- Handler怎麼進行執行緒通訊?Handler原理解讀執行緒
- python–執行緒同步原語Python執行緒
- 非同步VS多執行緒非同步執行緒