【譯】JVM 進(jìn)行線程同步背后的原理

本文翻譯自How the Java virtual machine performs thread synchronization幼驶,內(nèi)容略有刪改

前言

所有的 Java 程序都會(huì)被翻譯為包含字節(jié)碼的 class 文件,字節(jié)碼是 JVM 的機(jī)器語言畔咧。這篇文章將闡述 JVM 是如何處理線程同步以及相關(guān)的字節(jié)碼袖裕。

線程和共享數(shù)據(jù)

Java 的一個(gè)優(yōu)點(diǎn)就是在語言層面支持多線程触徐,這種支持集中在協(xié)調(diào)多線程對數(shù)據(jù)的訪問上明垢。

JVM 將運(yùn)行時(shí)數(shù)據(jù)劃分為幾個(gè)區(qū)域:一個(gè)或多個(gè)棧神得,一個(gè)堆,一個(gè)方法區(qū)饥侵。

在 JVM 中鸵赫,每個(gè)線程擁有一個(gè)棧,其他線程無法訪問躏升,里面的數(shù)據(jù)包括:局部變量辩棒,函數(shù)參數(shù),線程調(diào)用的方法的返回值膨疏。棧里面的數(shù)據(jù)只包含原生數(shù)據(jù)類型和對象引用一睁。在 JVM 中,不可能將實(shí)際對象的拷貝放入棧佃却。所有對象都在堆里面者吁。

JVM 只有一個(gè)堆,所有線程都共享它饲帅。堆中只包含對象复凳,把單獨(dú)的原生類型或者對象引用放入堆也是不可能的,除非它們是對象的一部分灶泵。數(shù)組也在堆中育八,包括原生類型的數(shù)組,因?yàn)樵?Java 中丘逸,數(shù)組也是對象单鹿。

除了棧和堆,另一個(gè)存放數(shù)據(jù)的區(qū)域就是方法區(qū)了深纲,它包含程序中使用到的所有類(靜態(tài))變量仲锄。方法區(qū)類似于棧,也只包含原生類型和對象引用湃鹊,但是又跟棧不同儒喊,方法區(qū)中類變量是線程共享的。

對象鎖和類鎖

正如前面所說币呵,JVM 中的兩個(gè)區(qū)域包含線程共享的數(shù)據(jù)怀愧,分別是:

  1. :包含所有對象
  2. 方法區(qū):包含所有類變量

如果多個(gè)線程需要同時(shí)使用同一個(gè)對象或者類變量,它們對數(shù)據(jù)的訪問必須被恰當(dāng)?shù)乜刂朴嘤7駝t芯义,程序會(huì)產(chǎn)生不可預(yù)測的行為。

為了協(xié)調(diào)多個(gè)線程對共享數(shù)據(jù)的訪問妻柒,JVM 給每個(gè)對象和類關(guān)聯(lián)了一個(gè)鎖扛拨。鎖就像是任意時(shí)間點(diǎn)只有一個(gè)線程能夠擁有的特權(quán)。如果一個(gè)線程想要鎖住一個(gè)特定的對象或者類举塔,它需要向 JVM 請求鎖绑警。線程向 JVM 請求鎖之后求泰,可能很快就拿到,或者過一會(huì)就拿到计盒,也可能永遠(yuǎn)拿不到渴频。當(dāng)線程不需要鎖之后,它把鎖還給 JVM北启。如果其他線程需要這個(gè)鎖卜朗,JVM 會(huì)交給該線程。

類鎖的實(shí)現(xiàn)其實(shí)跟對象鎖是一樣的暖庄。當(dāng) JVM 加載類文件的時(shí)候聊替,它會(huì)創(chuàng)建一個(gè)對應(yīng)類java.lang.Class對象。當(dāng)你鎖住一個(gè)類的時(shí)候培廓,你實(shí)際上是鎖住了這個(gè)類的Class對象。

線程訪問對象實(shí)例或者類變量的時(shí)候不需要獲取鎖春叫。但是如果一個(gè)線程獲取了一個(gè)鎖肩钠,其他線程不能訪問被鎖住的數(shù)據(jù),直到擁有鎖的線程釋放它暂殖。

管程

JVM 使用鎖和管程協(xié)作价匠。管程監(jiān)視一段代碼,保證一個(gè)時(shí)間點(diǎn)內(nèi)只有一個(gè)線程能執(zhí)行這段代碼呛每。

每個(gè)管程與一個(gè)對象引用關(guān)聯(lián)踩窖。當(dāng)線程到達(dá)管程監(jiān)視代碼段的第一條指令時(shí),線程必須獲取關(guān)聯(lián)對象的鎖晨横。線程不能執(zhí)行這段代碼直到它得到了鎖洋腮。一旦它得到了鎖,線程可以進(jìn)入被保護(hù)的代碼段手形。

當(dāng)線程離開被保護(hù)的代碼塊啥供,不管是如何離開的,它都會(huì)釋放關(guān)聯(lián)對象的鎖库糠。

多次鎖定

一個(gè)線程被允許鎖定一個(gè)對象多次伙狐。對于每個(gè)對象,JVM 維護(hù)了一個(gè)鎖的計(jì)數(shù)器瞬欧。沒有被鎖的對象計(jì)數(shù)為 0贷屎。當(dāng)一個(gè)線程第一次獲取鎖,計(jì)數(shù)器自增變?yōu)?1艘虎。每次這個(gè)線程(已經(jīng)得到鎖的線程)請求同一個(gè)對象的鎖唉侄,計(jì)數(shù)器都會(huì)自增。每次線程釋放鎖顷帖,計(jì)數(shù)器都會(huì)自減美旧。當(dāng)計(jì)數(shù)器變?yōu)?0 時(shí)渤滞,鎖才被釋放,可以給別的線程使用榴嗅。

同步塊

在 Java 語言的術(shù)語中妄呕,協(xié)調(diào)多個(gè)線程訪問共享數(shù)據(jù)被稱為同步(synchronization)。Java 提供了兩種內(nèi)建的方式來同步對數(shù)據(jù)的訪問:

  1. 同步語句
  2. 同步方法

同步語句

為了創(chuàng)建同步語句嗽测,你需要使用synchronized關(guān)鍵字绪励,括號里面是同步的對象引用,如下所示:

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;
            }
        }
    }
}

在上面的例子中唠粥,被同步塊包含的語句不會(huì)被執(zhí)行疏魏,直到線程得到this引用的對象鎖。如果不是鎖住this引用晤愧,而是鎖住其他對象大莫,在線程執(zhí)行同步塊語句之前,它需要獲得該對象的鎖官份。

有兩個(gè)字節(jié)碼monitorentermonitorexit只厘,被用來同步方法中的同步塊

字節(jié)碼 操作數(shù) 描述
monitorenter 取出對象引用舅巷,請求與對象引用關(guān)聯(lián)的鎖
monitorexit 取出對象引用羔味,釋放與對象引用關(guān)聯(lián)的鎖

當(dāng)monitorenter被 JVM 執(zhí)行時(shí),它請求棧頂對象引用關(guān)聯(lián)的鎖钠右。如果該線程已經(jīng)擁有該對象的鎖赋元,計(jì)數(shù)器自增。每次monitorexit被執(zhí)行飒房,計(jì)數(shù)器自減搁凸。當(dāng)計(jì)數(shù)器變?yōu)?0 時(shí),該鎖被釋放情屹。

注意:當(dāng)同步塊中拋出異常時(shí)坪仇,catch語句保證對象鎖被釋放。不管同步塊是如何退出的垃你,JVM 保證線程會(huì)釋放鎖椅文。

同步方法

為了同步整個(gè)方法,你只需要在方法聲明前面加上synchronized關(guān)鍵字惜颇。

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 不會(huì)使用特殊的字節(jié)碼來調(diào)用同步方法皆刺。當(dāng) JVM 解析方法的符號引用時(shí),它會(huì)判斷方法是不是同步的凌摄。如果是羡蛾,JVM 要求線程在調(diào)用之前請求鎖。對于實(shí)例方法锨亏,JVM 要求得到該實(shí)例對象的鎖痴怨。對于類方法忙干,JVM 要求得到類鎖。在同步方法完成之后浪藻,不管它是正常返回還是拋出異常捐迫,鎖都會(huì)被釋放。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末爱葵,一起剝皮案震驚了整個(gè)濱河市施戴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萌丈,老刑警劉巖璧瞬,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绽快,死亡現(xiàn)場離奇詭異,居然都是意外死亡螃征,警方通過查閱死者的電腦和手機(jī)垫挨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門缸血,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缓呛,“玉大人蛉迹,你說我怎么就攤上這事∮⒘耄” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵湿右,是天一觀的道長诅妹。 經(jīng)常有香客問我,道長毅人,這世上最難降的妖魔是什么吭狡? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮丈莺,結(jié)果婚禮上划煮,老公的妹妹穿的比我還像新娘。我一直安慰自己缔俄,他們只是感情好弛秋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著俐载,像睡著了一般蟹略。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上遏佣,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天挖炬,我揣著相機(jī)與錄音,去河邊找鬼状婶。 笑死意敛,一個(gè)胖子當(dāng)著我的面吹牛馅巷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播草姻,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼钓猬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碴倾?” 一聲冷哼從身側(cè)響起逗噩,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎跌榔,沒想到半個(gè)月后异雁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡僧须,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年纲刀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片担平。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡示绊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出暂论,到底是詐尸還是另有隱情面褐,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布取胎,位于F島的核電站展哭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闻蛀。R本人自食惡果不足惜匪傍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望觉痛。 院中可真熱鬧役衡,春花似錦、人聲如沸薪棒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盗尸。三九已至柑船,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泼各,已是汗流浹背鞍时。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逆巍。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓及塘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锐极。 傳聞我的和親對象是個(gè)殘疾皇子笙僚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司灵再,掛了不少肋层,但最終還是拿到小米、百度翎迁、阿里栋猖、京東、新浪汪榔、CVTE蒲拉、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,253評論 11 349
  • Java8張圖 11、字符串不變性 12痴腌、equals()方法雌团、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,707評論 0 11
  • 在一個(gè)方法內(nèi)部定義的變量都存儲(chǔ)在棧中士聪,當(dāng)這個(gè)函數(shù)運(yùn)行結(jié)束后锦援,其對應(yīng)的棧就會(huì)被回收,此時(shí)剥悟,在其方法體中定義的變量將不...
    Y了個(gè)J閱讀 4,418評論 1 14
  • Java繼承關(guān)系初始化順序 父類的靜態(tài)變量-->父類的靜態(tài)代碼塊-->子類的靜態(tài)變量-->子類的靜態(tài)代碼快-->父...
    第六象限閱讀 2,157評論 0 9
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理雨涛,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 15,608評論 3 83