深入理解Java多線程

本文主要就Hollis深入理解Java多線程系列文章的總結(jié)。原文詳見[HollisChuang's Blog邑飒,歡迎大家關(guān)注挽铁!

Synchronized實(shí)現(xiàn)原理

public class SynchronizedTest {

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

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

同步代碼塊使用monitorentermonitorexit兩個指令實(shí)現(xiàn)。執(zhí)行monitorenter指令加鎖,執(zhí)行monitorexit指令釋放鎖矢空。
同步方法使用ACC_SYNCHRONIZED關(guān)鍵字隱式的對方法加鎖,當(dāng)線程執(zhí)行的方法被標(biāo)上ACC_SYNCHRONIZED時,需要先獲得鎖才能執(zhí)行該方法窜骄。
每個對象維護(hù)一個計(jì)數(shù)器,記錄對象被鎖次數(shù)摆屯,當(dāng)一個線程獲得鎖時邻遏,該計(jì)數(shù)器自增1,當(dāng)同一個線程釋放鎖是虐骑,該計(jì)數(shù)器減1准验。

Java對象模型

每一個Java類在被JVM加載的時候,JVM會為這個類創(chuàng)建一個instanceKlass富弦,保存在方法區(qū)沟娱,用于在JVM層表示該Java類。當(dāng)我們使用new創(chuàng)建一個對象時腕柜,JVM會創(chuàng)建一個instanceOopDesc對象济似,包含兩部分信息:對象頭及元數(shù)據(jù)。對象頭中有一些運(yùn)行時數(shù)據(jù)盏缤,其中包括多線程相關(guān)鎖的信息砰蠢。元數(shù)據(jù)維護(hù)的指針指向?qū)ο笏鶎兕惖?strong>instanceKlass。

Java對象頭

class oopDesc {
    friend class VMStructs;
    private:
    volatile markOop  _mark;
    union _metadata {
        wideKlassOop    _klass;
        narrowOop       _compressed_klass;
  } _metadata;
}

markword設(shè)計(jì)是將存儲空間劃分為多個比特位唉铜,并在不同對象狀態(tài)下賦予比特位含義台舱。下圖為32為虛擬機(jī)


2018-12-12.16.04.25-ObjectHead.png

Monitor實(shí)現(xiàn)原理

同步方法和同步代碼塊都是基于monitor實(shí)現(xiàn)的。

操作系統(tǒng)中管程

管程是一種程序結(jié)構(gòu)潭流,結(jié)構(gòu)內(nèi)多個多個子程序(對象或模塊)形成多個共享線程互斥訪問共享資源竞惋。這些共享資源一般是硬件資源或者共享變量。管程實(shí)現(xiàn)了在一個時間點(diǎn)最多只有一個線程執(zhí)行管程中某個子程序灰嫉。

Java線程同步相關(guān)之Monitor

對象的所有方法互斥執(zhí)行拆宛,一個monitor只有一個運(yùn)行許可,任一線程進(jìn)入任何方法都需要獲得這個許可讼撒,離開時歸還許可浑厚。提供singal機(jī)制股耽,允許正在持有許可的線程放棄許可,等待某個條件成立后當(dāng)前線程可以通知等待這個條件變量的線程去重新獲取許可钳幅。

Monitor實(shí)現(xiàn)

Java虛擬機(jī)(HotSpot)的monitor是基于C++實(shí)現(xiàn)的物蝙,主要數(shù)據(jù)結(jié)構(gòu)如下

 ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

關(guān)鍵屬性如下:

_owner      // 指向持有ObjectMonitor對象的線程
_WaitSet    // 存放處于wait狀態(tài)的線程隊(duì)列
_EntryList  // 存在處于wait狀態(tài)的線程隊(duì)列
_recursion  // 鎖的重入次數(shù)
_count      // 用來記錄該線程獲取鎖的次數(shù)

當(dāng)多個線程同時訪問一段同步代碼時,會首先進(jìn)入EntryList 隊(duì)列中敢艰,當(dāng)某個線程獲取到對象的monitor后進(jìn)入Owner 區(qū)域并把ower 變量設(shè)置為當(dāng)前線程诬乞,同時monitor中計(jì)數(shù)器加1。即線程獲得鎖盖矫。
當(dāng)持有monitor的線程調(diào)用wait() 方法丽惭,將釋放當(dāng)前持有monitor,owner變量恢復(fù)為null辈双,count減1责掏,同時WaitSet集合中等待線程會被喚醒。當(dāng)前線程執(zhí)行完畢也會釋放monitor湃望。如下圖所示换衬。

2018-12-12.17.11.38-monitor.png

獲取鎖

void ATTR ObjectMonitor::enter(TRAPS) {
    Thread * const Self = THREAD ;
    void * cur ;
    //通過CAS嘗試把monitor的`_owner`字段設(shè)置為當(dāng)前線程
    cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
    //獲取鎖失敗
    if (cur == NULL) {assert (_recursions == 0, "invariant") ;
         assert (_owner      == Self, "invariant") ;
         // CONSIDER: set or assert OwnerIsThread == 1
         return ;
    }
    // 如果舊值和當(dāng)前線程一樣,說明當(dāng)前線程已經(jīng)持有鎖证芭,此次為重入瞳浦,_recursions自增,并獲得鎖废士。
    if (cur == Self) { 
        // TODO-FIXME: check for integer overflow!  BUGID 6557169.
        _recursions ++ ;
        return ;
    }

    // 如果當(dāng)前線程是第一次進(jìn)入該monitor叫潦,設(shè)置_recursions為1,_owner為當(dāng)前線程
    if (Self->is_lock_owned ((address)cur)) { 
        assert (_recursions == 0, "internal state error");
        _recursions = 1 ;
        // Commute owner from a thread-specific on-stack BasicLockObject address to
        // a full-fledged "Thread *".
        _owner = Self ;
        OwnerIsThread = 1 ;
        return ;
    }

    // 省略部分代碼官硝。
    // 通過自旋執(zhí)行ObjectMonitor::EnterI方法等待鎖的釋放
    for (;;) {
      jt->set_suspend_equivalent();
      // cleared by handle_special_suspend_equivalent_condition()
      // or java_suspend_self()

      EnterI (THREAD) ;

      if (!ExitSuspendEquivalent(jt)) break ;

      //
      // We have acquired the contended monitor, but while we were
      // waiting another thread suspended us. We don't want to enter
      // the monitor while suspended because that would surprise the
      // thread that suspended us.
      //
          _recursions = 0 ;
      _succ = NULL ;
      exit (Self) ;

      jt->java_suspend_self();
    }
}
2018-12-12.17.13.35-lockenter.png

釋放鎖

void ATTR ObjectMonitor::exit(TRAPS) {
   Thread * Self = THREAD ;
   //如果當(dāng)前線程不是Monitor的所有者
   if (THREAD != _owner) { 
     if (THREAD->is_lock_owned((address) _owner)) { // 
       // Transmute _owner from a BasicLock pointer to a Thread address.
       // We don't need to hold _mutex for this transition.
       // Non-null to Non-null is safe as long as all readers can
       // tolerate either flavor.
       assert (_recursions == 0, "invariant") ;
       _owner = THREAD ;
       _recursions = 0 ;
       OwnerIsThread = 1 ;
     } else {
       // NOTE: we need to handle unbalanced monitor enter/exit
       // in native code by throwing an exception.
       // TODO: Throw an IllegalMonitorStateException ?
       TEVENT (Exit - Throw IMSX) ;
       assert(false, "Non-balanced monitor enter/exit!");
       if (false) {
          THROW(vmSymbols::java_lang_IllegalMonitorStateException());
       }
       return;
     }
   }
    // 如果_recursions次數(shù)不為0.自減
   if (_recursions != 0) {
     _recursions--;        // this is simple recursive enter
     TEVENT (Inflated exit - recursive) ;
     return ;
   }

   //省略部分代碼矗蕊,根據(jù)不同的策略(由QMode指定),從cxq或EntryList中獲取頭節(jié)點(diǎn)氢架,通過ObjectMonitor::ExitEpilog方法喚醒該節(jié)點(diǎn)封裝的線程傻咖,喚醒操作最終由unpark完成。
2018-12-12.17.13.53-lockexit.png

注意 sychronized操作是重量級操作岖研,需要將用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)卿操。

Java虛擬機(jī)鎖優(yōu)化技術(shù)

線程狀態(tài)

分為以下五種,分別為:初始狀態(tài)(New)孙援,就緒狀態(tài)(Runnable)害淤,運(yùn)行狀態(tài)(Running)阻塞狀態(tài)(Blocked)拓售,死亡狀態(tài)(Dead)窥摄。各種狀態(tài)間轉(zhuǎn)換如下圖所示:

2018-12-12.17.27.29-thread.png

自旋鎖

線程不放棄處理器執(zhí)行時間,等待共享資源可訪問后繼續(xù)執(zhí)行邻辉,自旋鎖只是將當(dāng)前線程不停執(zhí)行循環(huán)體并檢查共享資源溪王,不改變線程狀態(tài)。

注意:線程數(shù)不停增加是導(dǎo)致性能下降值骇。

鎖消除

在使用synchronized時莹菱,如果使用JIT逃逸分析發(fā)現(xiàn)并無線程安全問題,則會使用鎖消除吱瘩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末道伟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子使碾,更是在濱河造成了極大的恐慌蜜徽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件票摇,死亡現(xiàn)場離奇詭異拘鞋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)矢门,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門盆色,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人祟剔,你說我怎么就攤上這事隔躲。” “怎么了物延?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵宣旱,是天一觀的道長。 經(jīng)常有香客問我叛薯,道長浑吟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任案训,我火速辦了婚禮买置,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘强霎。我一直安慰自己忿项,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布城舞。 她就那樣靜靜地躺著轩触,像睡著了一般。 火紅的嫁衣襯著肌膚如雪家夺。 梳的紋絲不亂的頭發(fā)上脱柱,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音拉馋,去河邊找鬼榨为。 笑死惨好,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的随闺。 我是一名探鬼主播日川,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼矩乐!你這毒婦竟也來了龄句?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤散罕,失蹤者是張志新(化名)和其女友劉穎分歇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欧漱,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡职抡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了硫椰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片繁调。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖靶草,靈堂內(nèi)的尸體忽然破棺而出蹄胰,到底是詐尸還是另有隱情,我是刑警寧澤奕翔,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布裕寨,位于F島的核電站,受9級特大地震影響派继,放射性物質(zhì)發(fā)生泄漏宾袜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一驾窟、第九天 我趴在偏房一處隱蔽的房頂上張望庆猫。 院中可真熱鬧,春花似錦绅络、人聲如沸月培。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杉畜。三九已至,卻和暖如春衷恭,著一層夾襖步出監(jiān)牢的瞬間此叠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工随珠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灭袁,地道東北人猬错。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像茸歧,于是被迫代替她去往敵國和親兔魂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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