java并發(fā)(6) 深入理解synchronized關(guān)鍵字

synchronized的三種形式

  1. 對于普通的同步方法, 鎖的是當(dāng)前的實(shí)例對象
  2. 對于靜態(tài)的同步方法, 鎖的是當(dāng)前的類對象
  3. 對于同步方法塊, 鎖的是給定傳入的對象(類或?qū)嵗?

synchronized的實(shí)現(xiàn)原理

同步塊中的實(shí)現(xiàn)

同步塊中, JVM的實(shí)現(xiàn)是以 monitorentermonitorexit 指令配對實(shí)現(xiàn)的.

當(dāng)進(jìn)入 monitorenter指令 時將嘗試獲取當(dāng)前對象的 monitor的持有權(quán),如果被其他線程占用而無法持有,當(dāng)前線程將被阻塞. monitorenter指令執(zhí)行結(jié)束,則表示當(dāng)前線程擁有了該對象的monitor持有權(quán).而monitorexit則表示釋放monitor的持有權(quán),即釋放了鎖.

// 源碼
    void lockBlock() {
        synchronized (Main.class) {

        }
    }

# 反匯編
  void lockBlock();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2
         2: dup
         3: astore_1
         4: monitorenter        # 進(jìn)入同步塊
         5: aload_1
         6: monitorexit         # 正常退出同步塊
         7: goto          15
        10: astore_2
        11: aload_1
        12: monitorexit         # 異常退出同步塊
        13: aload_2
        14: athrow
        15: return

同步方法中的實(shí)現(xiàn)

靜態(tài)同步方法和普通同步方法,是通過方法表結(jié)構(gòu)中的訪問標(biāo)識 access_flag設(shè)置為 ACC_SYNCHRONIZED來實(shí)現(xiàn)的.

當(dāng)方法調(diào)用時拓颓,調(diào)用指令將會 檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置蔼水,如果設(shè)置了,執(zhí)行線程將先持有monitor丢间, 然后再執(zhí)行方法姜挺,最后再方法完成(無論是正常完成還是非正常完成)時釋放monitor。

// 源碼
    synchronized void lockMethod() {

    }
# 反匯編
  synchronized void lockMethod();
    descriptor: ()V
    flags: (0x0020) ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return

synchronized使用注意事項(xiàng)

  1. 普通方法與同步方法調(diào)用互不關(guān)聯(lián)

當(dāng)一個線程進(jìn)入同步方法時,其他線程可以正常訪問其他非同步方法

  1. 所有同步方法同時只能被一個線程訪問

當(dāng)一個線程執(zhí)行同步方法時咧党,其他線程不能訪問該對象的任何同步方法

  1. 同一個鎖的同步代碼塊同一時刻只能被一個線程訪問

當(dāng)同步代碼塊都是同一個鎖時,方法可以被所有線程訪問陨亡,但同一個鎖的 同步代碼塊同一時刻只能被一個線程訪問.

    void lockBlock() {
        // todo... 這里所有的線程都能進(jìn)入
        synchronized (Main.class) {
            // todo... 同步代碼塊中,只有持有鎖的線程能進(jìn)入
        }
    }
  1. 線程間同時訪問同一個鎖的多個同步代碼的執(zhí)行順序不定

線程間同時訪問同一個鎖多個同步方法的執(zhí)行順序不定傍衡,即使是使用同一個對象鎖,因?yàn)橥椒椒ńY(jié)束后,鎖被釋放,所有線程都有可能競爭到鎖.

class Demo {
    @Synchronized
    fun synchronizedMethod1(name: String) {
        TimeUnit.MILLISECONDS.sleep(100)
        println("$name : synchronizedMethod1")
    }

    @Synchronized
    fun synchronizedMethod2(name: String) {
        TimeUnit.MILLISECONDS.sleep(100)
        println("$name : synchronizedMethod2")
    }
}

fun main(args: Array<String>) {
    val demo = Demo()
    thread {
        demo.synchronizedMethod1(Thread.currentThread().name)
        demo.synchronizedMethod2(Thread.currentThread().name)
    }
    thread {
        demo.synchronizedMethod2(Thread.currentThread().name)
        demo.synchronizedMethod1(Thread.currentThread().name)
    }
}

// ##########
Thread-0 : synchronizedMethod1
Thread-1 : synchronizedMethod1
Thread-1 : synchronizedMethod2
Thread-0 : synchronizedMethod2
  1. 不同鎖之間訪問非阻塞

對于鎖類對象和鎖實(shí)例對象的鎖以及不同對象實(shí)例的鎖,都互不相關(guān)的鎖负蠕,相互之間不會有任何影響

  1. 同步塊鎖基本變量或者字面量時,變量的值改變,鎖的對象也會發(fā)生改變.
fun method() {
    var lock = 1
    synchronized(lock) {
        // 如果此時 lock的值在外部被修改,如lock=2,值被修改成了2
        // 即使當(dāng)前同步方法沒有執(zhí)行完,其他等待的線程可以進(jìn)入,
        // 因?yàn)榇藭r鎖變成了 2 , 與之前的 1, 已經(jīng)不是一個鎖了
    }
}

synchronized 特點(diǎn)

  1. 獨(dú)占性(互斥鎖)

synchronized是屬于線程獨(dú)占類型的鎖,一個線程拿到對象的鎖后,其他線程試圖進(jìn)入對象鎖的臨界資源時蛙埂,將會處于阻塞狀態(tài).
如果 A方法中 去獲取B的鎖, B方法中去獲取A的鎖,會導(dǎo)致A,B相互等待,形成死鎖.

  1. 可重入性

當(dāng)一個線程再次請求自己持有對象鎖的臨界資源時,不需要通過鎖競爭,請求將會成功,這種情況屬于重入鎖.
因此在一個線程調(diào)用synchronized方法的同時在其方法體內(nèi)部調(diào)用該對象另一個synchronized方法遮糖,是允許的

  1. 非公平性

公平鎖 : 多個線程在等待同一個鎖時绣的,必須按照申請鎖的時間順序排隊(duì)等待.(cpu吞吐量較低)
非公平鎖 : 則不保證這點(diǎn),在鎖釋放時欲账,任何一個等待鎖的線程都有機(jī)會獲得鎖被辑。(cpu吞吐量高,極端情況下,某些等待線程將永遠(yuǎn)無法獲取到鎖)

  1. 不可中斷性

如果一個代碼塊被synchronized修飾了,當(dāng)一個線程獲取了對應(yīng)的鎖敬惦,并執(zhí)行該代碼塊時盼理,其他線程便只能一直等待,等待獲取鎖的線程釋放鎖.

等待的線程將不可被中斷(interrupt方法也無法生效),只有兩種情況下能釋放鎖.

  1. 獲取鎖的線程執(zhí)行完了該代碼塊俄删,然后線程釋放對鎖的占有
  2. 線程執(zhí)行發(fā)生異常宏怔,此時JVM會讓線程自動釋放鎖

jvm對synchronized的優(yōu)化

JDK1.5中,synchronized是性能低效的畴椰。因?yàn)檫@是一個重量級操作臊诊,它對性能最大的影響是阻塞的是實(shí)現(xiàn),掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成斜脂,這些操作給系統(tǒng)的并發(fā)性帶來了很大的壓力.

到了JDK1.6抓艳,發(fā)生了變化,對synchronized加入了很多優(yōu)化措施帚戳,有偏向鎖,輕量級鎖玷或,鎖消除,鎖升級片任,等等偏友。導(dǎo)致synchronized的效率大大的提升.

鎖的狀態(tài)總共有四種,無鎖狀態(tài)对供、偏向鎖位他、輕量級鎖和重量級鎖。隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖鹅髓,再升級的重量級鎖舞竿,但是鎖的升級是單向的,也就是說只能從低到高升級窿冯,不會出現(xiàn)鎖的降級.

對象頭

synchronized用的鎖是存在Java對象頭里的炬灭。如果對象是數(shù)組類型,則虛擬機(jī)用3個字寬(Word)存儲對象頭靡菇,如果對象是非數(shù)組類型,則用2字寬存儲對象頭米愿。在32位虛擬機(jī)中厦凤,1字寬等于4字節(jié),即32bit.

32位對象頭.png

在運(yùn)行期間育苟,Mark Word里存儲的數(shù)據(jù)會隨著鎖標(biāo)志位的變化而變化较鼓。Mark Word可能變
化為存儲以下4種數(shù)據(jù):

對象頭中鎖標(biāo)志.png

偏向鎖

在大多數(shù)情況下,鎖不僅不存在多線程競爭违柏,而且總是由同一線程多次獲得博烂,因此為了減少同一線程獲取鎖(會涉及到一些CAS操作,耗時)的代價而引入偏向鎖。

偏向鎖的核心思想是漱竖,如果一個線程獲得了鎖禽篱,那么鎖就進(jìn)入偏向模式,此時Mark Word 的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu)馍惹,當(dāng)這個線程再次請求鎖時躺率,無需再做任何同步操作 ,即獲取鎖的過程万矾,這樣就省去了大量有關(guān)鎖申請的操作悼吱,從而也就提供程序的性能。

對于鎖競爭比較激烈的場合良狈,偏向鎖將失效,這種情況下可能每次申請鎖的線程都是不相同的.偏向鎖失效后,會膨脹為輕量級鎖.

輕量級鎖

倘若偏向鎖失敗后添,虛擬機(jī)并不會立即升級為重量級鎖,它還會嘗試使用一種稱為輕量級鎖的優(yōu)化手段薪丁,此時Mark Word 的結(jié)構(gòu)也變?yōu)檩p量級鎖的結(jié)構(gòu)遇西。

線程嘗試使用CAS來講對象頭中的 Mark Word 替換為指向鎖記錄的指針.如果成功,表示當(dāng)前線程獲取到鎖,如果失敗,則表示其他線程正在競爭鎖,當(dāng)前線程嘗試使用自旋來獲取鎖(空循環(huán)50或100次),如果再失敗將膨脹值 重量級鎖.

即自旋+CAS操作來嘗試修改鎖標(biāo)志,成功則獲取到鎖,失敗則升級為重量級鎖.

鎖消除

消除鎖是虛擬機(jī)另外一種鎖的優(yōu)化,這種優(yōu)化更徹底严嗜,Java虛擬機(jī)在JIT編譯時(可以簡單理解為當(dāng)某段代碼即將第一次被執(zhí)行時進(jìn)行編譯努溃,又稱即時編譯),通過對運(yùn)行上下文的掃描阻问,去除不可能存在共享資源競爭的鎖梧税,通過這種方式消除沒有必要的鎖,可以節(jié)省毫無意義的請求鎖時間.

synchronized缺點(diǎn)

  1. 只能進(jìn)行互斥操作,不能實(shí)現(xiàn)共享鎖,在某些場景下效率低.比如對只讀數(shù)據(jù)的操作.
  2. 等待同步線程的不可中斷.
  3. 沒有超時獲取鎖
  4. 只能實(shí)現(xiàn)非公平鎖,無法實(shí)現(xiàn)公平鎖.

參考

CAS(Compare and Swap))

比較和交換. 需要輸入新值和舊值;如果內(nèi)存中的值與舊值相同,則內(nèi)存中的值更新為新值,否則不更新.
如果當(dāng)前狀態(tài)值等于預(yù)期值,則以原子方式將同步狀態(tài)設(shè)置為給定的更新值.
CAS操作具有volatile讀和寫的語義.

java中主要通過 UnSafe類的compareAndSwap系列方法來實(shí)現(xiàn),UnSafe是系統(tǒng)保護(hù)類,不能直接使用,使用時,需要通過反射調(diào)用.

cas 將在處理器中添加 LOCK_前綴的指令. 來保證執(zhí)行的原子性,禁止重排序,和刷新主內(nèi)存數(shù)據(jù).

三個問題 : ABA, 循環(huán)開銷大, 只保證一個共享變量的原子操作.

引用

  1. java并發(fā)編程的藝術(shù)

  2. 深入理解Java并發(fā)之synchronized實(shí)現(xiàn)原理

  3. Java 8 并發(fā)篇 - 冷靜分析 Synchronized(上)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市第队,隨后出現(xiàn)的幾起案子哮塞,更是在濱河造成了極大的恐慌,老刑警劉巖凳谦,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忆畅,死亡現(xiàn)場離奇詭異,居然都是意外死亡尸执,警方通過查閱死者的電腦和手機(jī)家凯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來如失,“玉大人绊诲,你說我怎么就攤上這事⊥使螅” “怎么了掂之?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脆丁。 經(jīng)常有香客問我世舰,道長,這世上最難降的妖魔是什么槽卫? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任跟压,我火速辦了婚禮,結(jié)果婚禮上歼培,老公的妹妹穿的比我還像新娘裆馒。我一直安慰自己,他們只是感情好丐怯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布喷好。 她就那樣靜靜地躺著,像睡著了一般读跷。 火紅的嫁衣襯著肌膚如雪梗搅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天效览,我揣著相機(jī)與錄音无切,去河邊找鬼。 笑死丐枉,一個胖子當(dāng)著我的面吹牛哆键,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瘦锹,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼籍嘹,長吁一口氣:“原來是場噩夢啊……” “哼闪盔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辱士,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤泪掀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后颂碘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體异赫,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年头岔,在試婚紗的時候發(fā)現(xiàn)自己被綠了塔拳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡峡竣,死狀恐怖靠抑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情澎胡,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布娩鹉,位于F島的核電站攻谁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏弯予。R本人自食惡果不足惜戚宦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锈嫩。 院中可真熱鬧受楼,春花似錦、人聲如沸呼寸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽对雪。三九已至河狐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瑟捣,已是汗流浹背馋艺。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留迈套,地道東北人捐祠。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像桑李,于是被迫代替她去往敵國和親踱蛀。 傳聞我的和親對象是個殘疾皇子窿给,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

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