經過synchronized來看Java鎖機制

1、何爲鎖住一個對象?

  1. 咱們常常說當使用synchronized 修飾一個代碼塊時,編譯後會在同步塊的先後分別造成monitorenter和monitorexit這兩個字節碼指令,而當虛擬機執行monitorenter指令時,會去嘗試獲取對象的鎖,若是這個對象沒有被鎖定或者當前線程已經持有了這個對象的鎖(可重入性),就會把鎖的計數器加一,而在執行monitorexit指令時鎖的計數器就會減一;
  2. 此時就很奇怪,我本身定義的對象哪來的鎖呢?實際上是由於在堆中存儲的每一個對象虛擬機都會爲其生成一個對象頭,對象頭通常分爲兩部分(若是是數組對象則會分爲三部分),而對這個問題最重要的是第一部分(Mark Word),通常爲32bit或64bit(由虛擬機的位數決定),當該對象處於未被鎖定的狀態時,MarkWord中有25bit用來存儲對象的hashCode(這裏猜想是爲了便於找到對象?與HashMap相似,但沒有深刻去了解),4bit用於存儲對象的分代年齡(GC相關),2bit用於存儲鎖標誌位,1bit默認爲0;

而當對象被不一樣種類的鎖鎖定時其狀態會變爲:(注意此時是複用原有的空間的,並經過棧保存原有的MarkWord信息以便以後解除鎖定的時候復原)

Java SE 1.6中,鎖一共有4種狀態,級別從低到高依次是: 無鎖狀態、偏向鎖狀態、輕量級鎖狀態(CAS操做實現)和重量級鎖狀態(synchronized),這幾個狀態會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是爲了提升得到鎖和釋放鎖的效率。

  1. 總結,當程序須要鎖定一個對象時,所謂鎖住一個對象即爲改變該對象的MarkWord狀態,讓其餘訪問該對象的線程瞭解到這個對象處於被鎖定的狀態。

2、當鎖住一個對象的時候有多個線程在等該如何去抉擇?

2.1 非公平性:

  1. synchronized是非公平鎖,即它不會按照線程等待的時間或其餘因素排除優先級,而是當持有鎖的對象釋放鎖後由一個隨機的對象去得到鎖;

圖片來源參考文章: blog.csdn.net/javazejian/…

每一個實例對象都會擁有一個等待隊列(即爲每一個實例準備的線程休息室),當線程處於鎖定狀態時,其餘線程須要等待獲取這個鎖時,會加入該對象的等待隊列(即圖中的EntryList),而後等待獲取鎖,即被Owner指針所指向,(若是此時被wait()方法掛起,則會進入WaitSet),若是中間沒有被掛起過,則最後會調用monitorexit()方法釋放鎖,並將鎖的計數器減一;java

  1. 那這些Owner指針,EntryList,WaitSet和鎖的計數器究竟存儲在哪裏呢?實際上是存儲在每一個實例對象都會對應的一個monitor對象裏面,而重量級鎖中(處在重量級鎖狀態時對象的MarkWord中)的指針即爲指向這個對象,這個monitor對象 是由 ObjectMonitor()對象實現的
ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //記錄個數
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //處於wait狀態的線程,會被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //處於等待鎖block狀態的線程,會被加入到該列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }
複製代碼

此時就能夠明白了,每一個對象對應的信息都是存儲在這的;數組

  1. 此時要注意Owner這個變量,由於輕量級鎖一樣涉及到,在處於輕量級鎖鎖定時,MarkWord中的指針爲持有該鎖的線程的棧幀中創建的一個叫鎖記錄(Lock Record)的空間,這個空間中只存放初始MarkWord及Owner指針(能夠看出輕量級鎖在內存方面的開銷也會小一些),若是當執行完同步代碼塊以後發現該指針已經變成指向monitor對象的指針時,說明有另外一個線程競爭了這個對象致使鎖膨脹(在變成重量級鎖前,競爭鎖的對象會適當自旋給定的次數,避免頻繁的線程掛起和喚醒由於Java虛擬機的線程是映射到操做系統核心態的線程的,因此每次對於線程的操做,都會須要將系統轉至核心態,而這個開銷是比較大的,而這也是重量級鎖慢的主要緣由),因此後面等待鎖的線程也要進入阻塞狀態;

2.2 使用ReentrantLock實現公平鎖和打斷條件

2.2.1 與synchronized的區別:

  1. 等待可中斷:即一個線程在等待獲取鎖的過程當中若是超過了必定的時間,這個線程能夠選擇放棄等待,改成處理其餘事情,而synchronized不能夠;
  2. 可實現公平鎖:固然ReentrantLock也能夠是非公平的,構造的時候能夠選擇;
  3. 鎖能夠綁定多個條件:能夠和Condition配合使用;
  4. 性能:synchronized在JDK1.6優化後性能與ReentrantLock至關;
  5. 實現方式:synchronized是基於JVM實現的,ReentrantLock是基於AQS實現的;

2.2.2 使用場景

  1. 除非是須要使用ReentrantLock的高級特性,不然仍是使用synchronized比較好,首先是由於前者須要在finally代碼塊中手動釋放鎖,而JVM會保證後者的鎖釋放;其次是由於如今二者的性能也比較接近;

3、鎖升級:

3.1 爲何會出現這個機制?

  1. 這種方案的提出主要是基於大部分時候,併發環境下的線程競爭比較少,因此可使用樂觀鎖的想法去優化悲觀鎖的性能;

3.2 鎖升級的過程:

  1. 偏向鎖:當一個對象第一次被一個線程訪問時,會將對象的MarkWord修改成偏向鎖,並將該線程Id使用CAS標識在MarkWord中(這裏爲何要用CAS改,是爲了防止兩個線程同時修改MarkWord,致使線程衝突);因此偏向鎖適用於預計只有一個線程訪問的代碼
  2. 輕量級鎖:當另外一個線程去訪問被偏向鎖鎖定的對象時發現MarkWord中的線程ID並非指向本身的,這個時候就會在安全點時Stop The Wrold,查看當前MarkWord中的線程是否還在運行,若是已經終止則將線程ID改成本身;若是以前的線程尚未中止運行,則須要解偏向鎖,升級成輕量級鎖;輕量級鎖適用於保護的代碼塊執行速度很快,且預計不會發生線程衝突的場景
  3. 重量級鎖:在多個線程競爭輕量級鎖,而且自旋失敗後,會升級成重量級鎖。
相關文章
相關標籤/搜索