JVM是如何處理線程安全的

所有Java程序都會(huì)被編譯成類文件,這些文件包含字節(jié)碼,即Java虛擬機(jī)的機(jī)器語(yǔ)言。 本文介紹了Java虛擬機(jī)如何處理線程同步,包括相關(guān)的字節(jié)碼。本文介紹了與線程同步相關(guān)的兩個(gè)機(jī)器碼,這兩個(gè)機(jī)器碼用于線程同步進(jìn)入和推出監(jiān)視器(enter monitor/exit monitor)。

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

Java的優(yōu)勢(shì)之一是在語(yǔ)言級(jí)別支持多線程。這種支持主要集中在協(xié)調(diào)對(duì)多個(gè)線程共享的數(shù)據(jù)的訪問(wèn)。

JVM將正在運(yùn)行的Java應(yīng)用程序的數(shù)據(jù)組織成幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域:一個(gè)或者多個(gè)線程棧、一個(gè)堆和一個(gè)方法區(qū)。

在JVM內(nèi)部,每一個(gè)線程被分配一個(gè)棧,用于存放此線程單獨(dú)使用的數(shù)據(jù),包括本地變量表、參數(shù)和該線程調(diào)用的每一個(gè)方法的返回值。棧中的數(shù)據(jù)僅僅包括原始數(shù)據(jù)類型和對(duì)象引用。在JVM中,不可能將實(shí)際對(duì)象放在線程棧上,所有的對(duì)象都在堆上。

在JVM上只有一個(gè)堆,所有線程共享此區(qū)域。堆上存放了所有的對(duì)象。無(wú)法在堆上放置單獨(dú)的原語(yǔ)類型或?qū)ο笠?-除非這些是其他對(duì)象的組成部分。數(shù)組也是在堆里的,包括原始類型的數(shù)組,但是在Java中,數(shù)組本身也是對(duì)象。

除了棧和堆,在JVM中存放數(shù)據(jù)的還有方法區(qū),其包含所有的類(class)變量和靜態(tài)變量。方法區(qū)和棧很相似,也僅僅包括原始數(shù)據(jù)類型和對(duì)象引用。但是類變量是所有線程共享的。

對(duì)象級(jí)鎖和類級(jí)鎖

通過(guò)上面的描述,JVM中有兩塊內(nèi)存區(qū)域包含了所有線程能共享訪問(wèn)的數(shù)據(jù),它們是:

  • 堆,包含了所有的對(duì)象
  • 方法區(qū),包含了所有的類變量和靜態(tài)變量

如果多個(gè)線程需要并發(fā)的使用同一個(gè)對(duì)象或者類變量,必須要對(duì)他們使用數(shù)據(jù)做合適的管理。否則,程序?qū)⒊霈F(xiàn)不可預(yù)測(cè)的結(jié)果。

為了協(xié)調(diào)多個(gè)線程之間的共享數(shù)據(jù)訪問(wèn),JVM將鎖與每個(gè)對(duì)象和類變量關(guān)聯(lián)。 鎖就像特權(quán),一次只能有一個(gè)線程“擁有”。 如果線程想要鎖定特定的對(duì)象或類,它將向JVM申請(qǐng)。當(dāng)線程不再需要鎖時(shí),它將鎖還給JVM。 如果另一個(gè)線程請(qǐng)求了相同的鎖,則JVM將鎖傳遞給該線程。

類級(jí)別鎖的實(shí)現(xiàn)實(shí)際上和對(duì)象鎖一樣。當(dāng)JVM加載一個(gè)class文件時(shí),它創(chuàng)建了一個(gè)java.lang.Class的實(shí)例。當(dāng)你鎖定一個(gè)類時(shí),你實(shí)際上鎖定的時(shí)那個(gè)類的Class對(duì)象。

線程無(wú)需獲取鎖即可訪問(wèn)實(shí)例或類變量。 但是,如果某個(gè)線程確實(shí)獲得了鎖定,則其他線程無(wú)法訪問(wèn)鎖定的數(shù)據(jù),直到擁有該鎖定的線程將其釋放為止。

監(jiān)視器

JVM借助于監(jiān)視器來(lái)使用鎖。監(jiān)視器基本上是一個(gè)守護(hù)者,因?yàn)樗O(jiān)視一串代碼序列,確保一次只有一個(gè)線程執(zhí)行代碼。

每個(gè)監(jiān)視器都與一個(gè)對(duì)象引用關(guān)聯(lián)。 當(dāng)線程到達(dá)監(jiān)視器監(jiān)視下的代碼塊中的第一條指令時(shí),該線程必須獲得對(duì)引用對(duì)象的鎖定。 線程獲得鎖之前,不允許執(zhí)行代碼。 一旦獲得了鎖,線程便進(jìn)入受保護(hù)的代碼塊。當(dāng)線程離開(kāi)該塊時(shí),無(wú)論它如何離開(kāi)該塊,它都會(huì)釋放關(guān)聯(lián)對(duì)象上的鎖。

多重鎖

允許單個(gè)線程多次鎖定同一對(duì)象。 對(duì)于每個(gè)對(duì)象,JVM都會(huì)對(duì)該對(duì)象被鎖定的次數(shù)進(jìn)行計(jì)數(shù)。 一個(gè)未鎖定的對(duì)象的計(jì)數(shù)為零。 當(dāng)線程首次獲取鎖時(shí),計(jì)數(shù)將增加為一。 線程每次在同一對(duì)象上獲取鎖時(shí),計(jì)數(shù)都會(huì)增加。 每次線程釋放鎖定時(shí),計(jì)數(shù)都會(huì)減少。 當(dāng)計(jì)數(shù)達(dá)到零時(shí),將釋放該鎖并將其提供給其他線程。

同步塊

在Java語(yǔ)言術(shù)語(yǔ)中,將訪問(wèn)共享數(shù)據(jù)的多個(gè)線程的協(xié)調(diào)稱為同步。 該語(yǔ)言提供了兩種內(nèi)置的方法來(lái)同步對(duì)數(shù)據(jù)的訪問(wèn):使用同步語(yǔ)句或同步方法。

同步語(yǔ)句塊

通過(guò)synchronized關(guān)鍵字修飾包含有一個(gè)對(duì)象引用的表達(dá)式來(lái)創(chuàng)建一個(gè)同步語(yǔ)句,例如下面的代碼:

class SyncStatementsDemo{
    private List<String> demoLst=new ArraysList<String>(10);
    void syncMethod(){
        synchronized(this){
            if(demoLst!=null&&demoLst.size()>0){
                demoLst[1]=demoLst[2];
            }
        }
    }
}

在上述情況下,直到在當(dāng)前對(duì)象(this)上獲得鎖后,才會(huì)執(zhí)行同步塊中包含的語(yǔ)句。 如果表達(dá)式產(chǎn)生了對(duì)另一個(gè)對(duì)象的引用,而不是這個(gè)this引用,那么在線程繼續(xù)之前,將獲取與該對(duì)象相關(guān)聯(lián)的鎖。

兩個(gè)機(jī)器碼,monitorentermonitorexit,被用于方法同步塊,解釋如下:

機(jī)器碼 描述
monitorenter 彈出對(duì)象引用,獲取與該對(duì)象引用關(guān)聯(lián)的鎖
monitorexit 彈出對(duì)象引用,釋放與該對(duì)象引用關(guān)聯(lián)的鎖

當(dāng)JVM遇到monitorenter時(shí),它將獲取堆棧上對(duì)象引用的對(duì)象的鎖。 如果線程已經(jīng)擁有該對(duì)象的鎖,則將增加一個(gè)計(jì)數(shù)。 每次對(duì)對(duì)象上的線程執(zhí)行monitorexit時(shí),計(jì)數(shù)都會(huì)減少。 當(dāng)計(jì)數(shù)達(dá)到零時(shí),將釋放監(jiān)視器。

請(qǐng)注意,即使同步塊內(nèi)發(fā)生異常,catch子句也可以確保鎖定的對(duì)象將被解鎖釋放。 無(wú)論同步塊如何退出,線程進(jìn)入該塊時(shí)所獲得的對(duì)象鎖定都將被釋放。

同步方法

僅僅需要在方法修飾符上加上synchronized就可以同步整個(gè)方法,如下:

class SyncStatementsDemo{
    private List<String> demoLst=new ArraysList<String>(10);
    synchronized void syncMethod(){
        if(demoLst!=null&&demoLst.size()>0){
            demoLst[1]=demoLst[2];
        }
    }
}

JVM不使用任何特殊的操作碼來(lái)調(diào)用同步方法或從同步方法返回。 當(dāng)JVM解析對(duì)方法的符號(hào)引用時(shí),它將確定該方法是否時(shí)同步的。 如果是,則JVM在調(diào)用該方法之前獲取一個(gè)鎖。 對(duì)于實(shí)例方法,JVM獲取與在其上調(diào)用該方法的對(duì)象關(guān)聯(lián)的鎖。 對(duì)于類方法,它獲取與該方法所屬的類關(guān)聯(lián)的鎖。 同步方法完成后,無(wú)論是通過(guò)返回還是方法拋出異常來(lái)完成,都將釋放該鎖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容