前言所有的 Java 程序都會被翻譯為包含字節(jié)碼的 class 文件,字節(jié)碼是 JVM 的機器語言,。這篇文章將闡述 JVM 是如何處理線程同步以及相關(guān)的字節(jié)碼,。 線程和共享數(shù)據(jù)Java 的一個優(yōu)點就是在語言層面支持多線程,這種支持集中在協(xié)調(diào)多線程對數(shù)據(jù)的訪問上,。 JVM 將運行時數(shù)據(jù)劃分為幾個區(qū)域:一個或多個棧,,一個堆,一個方法區(qū),。 在 JVM 中,,每個線程擁有一個棧,其他線程無法訪問,,里面的數(shù)據(jù)包括:局部變量,,函數(shù)參數(shù),線程調(diào)用的方法的返回值,。棧里面的數(shù)據(jù)只包含原生數(shù)據(jù)類型和對象引用,。在 JVM 中,,不可能將實際對象的拷貝放入棧。所有對象都在堆里面,。 JVM 只有一個堆,,所有線程都共享它。堆中只包含對象,,把單獨的原生類型或者對象引用放入堆也是不可能的,,除非它們是對象的一部分。數(shù)組也在堆中,,包括原生類型的數(shù)組,,因為在 Java 中,數(shù)組也是對象,。 除了棧和堆,,另一個存放數(shù)據(jù)的區(qū)域就是方法區(qū)了,它包含程序中使用到的所有類(靜態(tài))變量,。方法區(qū)類似于棧,,也只包含原生類型和對象引用,但是又跟棧不同,,方法區(qū)中類變量是線程共享的,。 對象鎖和類鎖正如前面所說,JVM 中的兩個區(qū)域包含線程共享的數(shù)據(jù),,分別是:
如果多個線程需要同時使用同一個對象或者類變量,,它們對數(shù)據(jù)的訪問必須被恰當?shù)乜刂啤7駝t,,程序會產(chǎn)生不可預(yù)測的行為,。 為了協(xié)調(diào)多個線程對共享數(shù)據(jù)的訪問,JVM 給每個對象和類關(guān)聯(lián)了一個鎖,。鎖就像是任意時間點只有一個線程能夠擁有的特權(quán),。如果一個線程想要鎖住一個特定的對象或者類,它需要向 JVM 請求鎖,。線程向 JVM 請求鎖之后,可能很快就拿到,,或者過一會就拿到,,也可能永遠拿不到。當線程不需要鎖之后,,它把鎖還給 JVM,。如果其他線程需要這個鎖,JVM 會交給該線程,。 類鎖的實現(xiàn)其實跟對象鎖是一樣的,。當 JVM 加載類文件的時候,,它會創(chuàng)建一個對應(yīng)類 線程訪問對象實例或者類變量的時候不需要獲取鎖。但是如果一個線程獲取了一個鎖,,其他線程不能訪問被鎖住的數(shù)據(jù),,直到擁有鎖的線程釋放它。 管程JVM 使用鎖和管程協(xié)作,。管程監(jiān)視一段代碼,,保證一個時間點內(nèi)只有一個線程能執(zhí)行這段代碼。 每個管程與一個對象引用關(guān)聯(lián),。當線程到達管程監(jiān)視代碼段的第一條指令時,,線程必須獲取關(guān)聯(lián)對象的鎖。線程不能執(zhí)行這段代碼直到它得到了鎖,。一旦它得到了鎖,,線程可以進入被保護的代碼段。 當線程離開被保護的代碼塊,,不管是如何離開的,,它都會釋放關(guān)聯(lián)對象的鎖。 多次鎖定一個線程被允許鎖定一個對象多次,。對于每個對象,,JVM 維護了一個鎖的計數(shù)器。沒有被鎖的對象計數(shù)為 0,。當一個線程第一次獲取鎖,,計數(shù)器自增變?yōu)?1。每次這個線程(已經(jīng)得到鎖的線程)請求同一個對象的鎖,,計數(shù)器都會自增,。每次線程釋放鎖,計數(shù)器都會自減,。當計數(shù)器變?yōu)?0 時,,鎖才被釋放,可以給別的線程使用,。 同步塊在 Java 語言的術(shù)語中,,協(xié)調(diào)多個線程訪問共享數(shù)據(jù)被稱為同步(synchronization)。Java 提供了兩種內(nèi)建的方式來同步對數(shù)據(jù)的訪問:
同步語句為了創(chuàng)建同步語句,,你需要使用 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; } } }} 在上面的例子中,被同步塊包含的語句不會被執(zhí)行,,直到線程得到 有兩個字節(jié)碼
當 注意:當同步塊中拋出異常時,, 同步方法為了同步整個方法,,你只需要在方法聲明前面加上 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 不會使用特殊的字節(jié)碼來調(diào)用同步方法,。當 JVM 解析方法的符號引用時,,它會判斷方法是不是同步的。如果是,,JVM 要求線程在調(diào)用之前請求鎖,。對于實例方法,JVM 要求得到該實例對象的鎖,。對于類方法,,JVM 要求得到類鎖。在同步方法完成之后,,不管它是正常返回還是拋出異常,,鎖都會被釋放。 |
|