深入理解多線程(一)——Synchronized的實(shí)現(xiàn)原理

原文轉(zhuǎn)載:http://www.hollischuang.com/archives/1883

synchronized,是Java中用于解決并發(fā)情況下數(shù)據(jù)同步訪問(wèn)的一個(gè)很重要的關(guān)鍵字。當(dāng)我們想要保證一個(gè)共享資源在同一時(shí)間只會(huì)被一個(gè)線程訪問(wèn)到時(shí),我們可以在代碼中使用synchronized關(guān)鍵字對(duì)類(lèi)或者對(duì)象加鎖。那么,本文來(lái)介紹一下synchronized關(guān)鍵字的實(shí)現(xiàn)原理是什么。在閱讀本文之間,建議先看下Java虛擬機(jī)是如何執(zhí)行線程同步的

反編譯

眾所周知,在Java中,synchronized有兩種使用形式,同步方法和同步代碼塊。代碼如下:

/**
 * @author Hollis 17/11/9.
 */
public class SynchronizedTest {

    public synchronized void doSth(){
        System.out.println("Hello World");
    }

    public void doSth1(){
        synchronized (SynchronizedTest.class){
            System.out.println("Hello World");
        }
    }
}

我們先來(lái)使用Javap來(lái)反編譯以上代碼,結(jié)果如下(部分無(wú)用信息過(guò)濾掉了):

  public synchronized void doSth();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

  public void doSth1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #5                  // class com/hollis/SynchronizedTest
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #3                  // String Hello World
        10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit
        21: aload_2
        22: athrow
        23: return

反編譯后,我們可以看到Java編譯器為我們生成的字節(jié)碼。在對(duì)于doSthdoSth1的處理上稍有不同。也就是說(shuō)。JVM對(duì)于同步方法和同步代碼塊的處理方式不同。

對(duì)于同步方法,JVM采用ACC_SYNCHRONIZED標(biāo)記符來(lái)實(shí)現(xiàn)同步。 對(duì)于同步代碼塊。JVM采用monitorentermonitorexit兩個(gè)指令來(lái)實(shí)現(xiàn)同步。

關(guān)于這部分內(nèi)容,在JVM規(guī)范中也可以找到相關(guān)的描述。

同步方法

The Java? Virtual Machine Specification中有關(guān)于方法級(jí)同步的介紹:

Method-level synchronization is performed implicitly, as part of method invocation and return. A synchronized method is distinguished in the run-time constant pool’s method_info structure by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.

主要說(shuō)的是: 方法級(jí)的同步是隱式的。同步方法的常量池中會(huì)有一個(gè)ACC_SYNCHRONIZED標(biāo)志。當(dāng)某個(gè)線程要訪問(wèn)某個(gè)方法的時(shí)候,會(huì)檢查是否有ACC_SYNCHRONIZED,如果有設(shè)置,則需要先獲得監(jiān)視器鎖,然后開(kāi)始執(zhí)行方法,方法執(zhí)行之后再釋放監(jiān)視器鎖。這時(shí)如果其他線程來(lái)請(qǐng)求執(zhí)行方法,會(huì)因?yàn)闊o(wú)法獲得監(jiān)視器鎖而被阻斷住。值得注意的是,如果在方法執(zhí)行過(guò)程中,發(fā)生了異常,并且方法內(nèi)部并沒(méi)有處理該異常,那么在異常被拋到方法外面之前監(jiān)視器鎖會(huì)被自動(dòng)釋放。

同步代碼塊

同步代碼塊使用monitorentermonitorexit兩個(gè)指令實(shí)現(xiàn)。 The Java? Virtual Machine Specification 中有關(guān)于這兩個(gè)指令的介紹:

monitorenter

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.

If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

monitorexit

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

大致內(nèi)容如下: 可以把執(zhí)行monitorenter指令理解為加鎖,執(zhí)行monitorexit理解為釋放鎖。 每個(gè)對(duì)象維護(hù)著一個(gè)記錄著被鎖次數(shù)的計(jì)數(shù)器。未被鎖定的對(duì)象的該計(jì)數(shù)器為0,當(dāng)一個(gè)線程獲得鎖(執(zhí)行monitorenter)后,該計(jì)數(shù)器自增變?yōu)?1 ,當(dāng)同一個(gè)線程再次獲得該對(duì)象的鎖的時(shí)候,計(jì)數(shù)器再次自增。當(dāng)同一個(gè)線程釋放鎖(執(zhí)行monitorexit指令)的時(shí)候,計(jì)數(shù)器再自減。當(dāng)計(jì)數(shù)器為0的時(shí)候。鎖將被釋放,其他線程便可以獲得鎖。

總結(jié)

同步方法通過(guò)ACC_SYNCHRONIZED關(guān)鍵字隱式的對(duì)方法進(jìn)行加鎖。當(dāng)線程要執(zhí)行的方法被標(biāo)注上ACC_SYNCHRONIZED時(shí),需要先獲得鎖才能執(zhí)行該方法。

同步代碼塊通過(guò)monitorentermonitorexit執(zhí)行來(lái)進(jìn)行加鎖。當(dāng)線程執(zhí)行到monitorenter的時(shí)候要先獲得所鎖,才能執(zhí)行后面的方法。當(dāng)線程執(zhí)行到monitorexit的時(shí)候則要釋放鎖。

每個(gè)對(duì)象自身維護(hù)這一個(gè)被加鎖次數(shù)的計(jì)數(shù)器,當(dāng)計(jì)數(shù)器數(shù)字為0時(shí)表示可以被任意線程獲得鎖。當(dāng)計(jì)數(shù)器不為0時(shí),只有獲得鎖的線程才能再次獲得鎖。即可重入鎖。

至此,我們大致了解了Synchronized的原理。但是還有幾個(gè)問(wèn)題并沒(méi)有介紹清楚,比如,Monitor到底是什么?對(duì)象的鎖的狀態(tài)保存在哪里? 別急,后面會(huì)再介紹。

(全文完)
下一篇:深入理解多線程(二)—— Java的對(duì)象模型

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,442評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,835評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,581評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,922評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,096評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,374評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,591評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,789評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,196評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,524評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,322評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,554評(píng)論 2 379