java的鎖是經過C++的CAS機制完成的。JVM隱藏了底層細節,提供synchronized關鍵字給工程師實現同步,其中包含了:偏向鎖,輕量鎖,重量鎖等狀態。而JDK5以後的concurrent包裏面的lock,atomic,semaphore則是經過CAS來實現,比JVM的鎖更加高效。下面記錄下synchronized的偏向鎖、輕量鎖、重量鎖。java
CAS(Compare and Swap):比較設置。用於在硬件層面上提供原子性操做。在intel處理器中,比較並交換經過指令cmpxchg實現。比較是否和給定的數值一致,若是一致則修改,不一致則不修改。數組
JVM規範規定:JVM基於進入和退出Monitor對象來實現方法同步和代碼塊同步,但二者的實現細節不同。安全
代碼塊同步是使用monitorenter和monitorexit指令實現,而方法同步是使用另一種方式實現的,細節在JVM規範裏並無詳細說明,可是方法的同步一樣可使用這兩個指令來實現。多線程
monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處, JVM要保證每一個monitorenter必須有對應的monitorexit與之配對。任何對象都有一個 monitor 與之關聯,當且一個monitor 被持有後,它將處於鎖定狀態。併發
線程執行到 monitorenter 指令時,將會嘗試獲取對象所對應的 monitor 的全部權,即嘗試得到對象的鎖。高併發
java的併發同步關鍵字(synchronized)以及lock、aotmic,semaphore都是經過CAS機制構建的。不一樣在於synchronized是JVM直接隱藏了實現細節,方便工程師使用,包含了:偏向鎖、輕量鎖、重量鎖。而最新的concurrent包是直接將cas操做交給工程師,以此實現高併發。性能
java的對象頭主要用來存放java對象在heep空間中的一些重要信息,固然也包括了對象鎖信息。一、對象是數組類型,則虛擬機用3個Word(字寬)存儲對象頭。二、對象是非數組類型,則用2字寬存儲對象頭。在32位虛擬機中,一字寬(機器寬度)等於四字節,即32bit。測試
其中的Mark World字節用來存放對象的hashCode,分代年齡、所標記。
網站
32位的Mark World的默認(無鎖狀態)結構以下:
atom
有鎖狀態:
a、32位
b、64位
Synchronized經過JVM的鎖機制來實現,爲了減小鎖的獲取和釋放帶來的性能消耗,引入了「偏向鎖」、「輕量鎖」。因此在javase1.6以後有四種狀態:無鎖、偏向鎖狀態、輕量鎖狀態、重量鎖狀態。它們會隨着競爭逐漸升級,可是鎖不能降級。
a、偏向鎖:(無CAS機制:不須要獲取moniter)
Hotspot的做者通過以往的研究發現大多數狀況下鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖。
流程:
一、偏向鎖獲取:當一個線程訪問同步塊並獲取鎖時,會在對象頭和棧幀中的鎖記錄裏存儲鎖偏向的線程ID,之後該線程在進入和退出同步塊時不須要花費CAS操做來加鎖和解鎖,而只需簡單的測試一下對象頭的Mark Word裏是否存儲着指向當前線程的偏向鎖(直接先去匹配鎖的線程ID是否和當前線程ID相同--直接檢測結果)。
若是測試成功,表示線程已經得到了鎖;
若是測試失敗,則須要再測試下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖)。若是沒有設置,則使用CAS競爭鎖(輕量鎖);若是設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。(也就是當前對象鎖使用的是偏向鎖,則將其修改並指向本身。若是偏向鎖沒有被使用,則直接使用輕量鎖來獲取Monitor)
二、 偏向鎖的撤銷:偏向鎖使用了一種等到競爭出現才釋放鎖的機制,因此當線程B嘗試競爭偏向鎖時,持有偏向鎖的線程A纔會釋放鎖。偏向鎖的撤銷,須要等待全局安全點(在這個時間點上沒有字節碼正在執行):它會首先暫停擁有偏向鎖的線程A,而後檢查持有偏向鎖A的線程是否活着,若是線程不處於活動狀態,則將對象頭設置成無鎖狀態,若是線程仍然活着,擁有偏向鎖的棧會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要麼從新偏向於其餘線程,要麼恢復到無鎖或者標記對象不適合做爲偏向鎖,最後喚醒暫停的線程。
(例如:線程A全部獲取偏向鎖,操做完了直接進行其它操做,不釋放(節約性能)。線程B須要獲取偏向鎖,首先經過CAS發現偏向鎖狀態爲1,說明當前對象是使用的偏向鎖。而後通知JVM,JVM在全局安全點上。暫停線程A,讓其釋放偏向鎖)
關閉偏向鎖:偏向鎖在Java 6和Java 7裏是默認啓用的,可是它在應用程序啓動幾秒鐘以後才激活,若有必要可使用JVM參數來關閉延遲-XX:BiasedLockingStartupDelay = 0。若是你肯定本身應用程序裏全部的鎖一般狀況下處於競爭狀態,能夠經過JVM參數關閉偏向鎖-XX:-UseBiasedLocking=false,那麼默認會進入輕量級鎖狀態。
b、輕量鎖和重量鎖(用CAS機制來獲取對象的Monitor)
一、輕量級鎖加鎖:線程在執行同步塊以前,JVM會先在當前線程的棧楨中建立用於存儲鎖記錄的空間,並將對象頭中的Mark Word複製到鎖記錄中,官方稱爲Displaced Mark Word。而後線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
ps:自旋至關於
public volatitle boolean flag=false; public void getSyn(){ while(!flag){ flag=true; //dosomething flg=false; } } 一直不斷的循環,想進行操做
二、輕量級鎖解鎖:輕量級解鎖時,會使用原子的CAS操做來將Displaced Mark Word替換回到對象頭,若是成功,則表示沒有競爭發生。若是失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。
PS:
獲取: 線程A獲取輕量鎖,將Mark World複製到線程A的鎖存儲空間,修改鎖狀態爲00,指針指向線程A的Mark World(輕量鎖)。
線程B想獲取Monitor,可是鎖狀態爲00。就會一直自旋,直到達到必定次數(JVM決定),將鎖狀態修改成01(重量鎖:線程B阻塞狀態)
釋放:線程A操做完成後,發現鎖狀態是00(不是重量鎖:線程B阻塞狀態)則。將Mark World複製回去。若是鎖狀態是01(線程B處於阻塞狀態),則直接Mark World複製回去、將阻塞線程釋放(將其踢出等待隊列,回到start()狀態)。最後修改鎖狀態爲11.
c、鎖的比較
綜合理解synchronized的不一樣鎖:
一、偏向鎖:使用完後不須要當即釋放,效率很高。且獲取方式不須要複製Monitor。
二、輕量鎖和重量鎖:使用完須要當即釋放鎖,重量鎖狀態就是線程阻塞狀態。
三、Monitor的獲取(複製)和釋放(複製回去)都須要很高的CPU資源。
四、追求響應速度:例如網站,須要很快的響應時間。這也是concurrent包裏面的locak、aotmic、semaphore大量使用的緣由。(CPU須要拿更多的時間去切換線程來不斷的CAS,判斷是否能獲取鎖)
五、吞吐量:例如科學計算,不最求響應時間,追求的是運算量。(CPU大部分時間拿去計算)
參考:
java的Monitor機制:http://www.ibm.com/developerworks/cn/java/j-lo-synchronized/
方騰飛:聊聊併發