上篇文章咱們從硬件級別探索,對可見性和有序性的認識上升了一個高度,卻遲遲沒有介紹原子性的解決方案。java
今天咱們就來聊一聊原子性的解決方案,鎖。編程
引入鎖機制,除了能夠保證原子性,同時也能夠保證可見性和有序性。併發
相信小夥伴們對於synchronized互斥鎖必定很熟悉,可是你懂它的實現原理嗎,今天就讓咱們一塊兒來揭開它的神祕面紗吧。app
首先咱們來看一下synchronized是怎麼保證原子性的。jvm
其實往最簡單瞭解釋,仍是比較容易理解的。synchronized加鎖主要靠的是monitor,monitor在java裏能夠理解成一個監視器,在操做系統裏它又被稱爲管程。佈局
簡單的模型以下圖:優化
當咱們的程序經過synchronized鎖定一個對象的時候,這個對象會關聯一個monitor,獲取鎖時會對monitor中的計數器進行+1操做,釋放鎖的時候進行-1操做,同時也是支持可重入的,同一個線程再次獲取該對象的鎖,計數器就再+1。ui
若是計數器爲0就表明徹底釋放了鎖,其餘線程能夠獲取鎖。this
若是線程調用了wait方法,會釋放鎖資源,同時把線程放入waitset中,等待notifyall方法喚醒,喚醒後從新開始競爭鎖資源。spa
這就是sychronized鎖的最簡單的解釋,咱們固然不會知足於此,接下來咱們繼續深刻研究一下。
先看一段代碼:
MyLock lock = new MyLock();//一個自定義的鎖對象 sychronized(lock){ //... }
java的對象在內存中存儲的佈局能夠分爲三塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。
對象頭包含Mark Word(包含hashcode、鎖數據、GC信息等)和Class MetaData Address(指向Class對象的指針)。
實例數據就是咱們在對象裏存放的那些數據。
java要求對象大小爲8字節的整數倍,對齊填充就是用來填充字節的,沒有其餘意義。
Mark Word會指向一個monitor,這個monitor是C++實現的一個Object Monitor對象,首先線程在獲取鎖時,先進入到entry list中,而後經過CAS對count計數器進行+1操做,若是+1成功表明獲取到鎖,此時就會把該線程的信息放入owner中,owner就是用來存儲當前獲取到鎖的線程的。總體結構如圖所示:
在說可見性以前,咱們先引入兩個概念:Store屏障和Load屏障。
Store屏障就是強制CPU執行flush操做,Load屏障就是強制CPU執行refresh操做。
flush和refresh咱們上篇文章已經說過,這裏就再也不解釋了。
那sychronized是如何實現可見性的呢,其實就是利用了內存屏障。以下:
sychronized(this){ // monitorenter // Load內存屏障 //... } //monitorexit //Store內存屏障
一樣在說有序性以前引入兩個新的內存屏障:Acquire屏障和Release屏障。
Acquire屏障能夠禁止讀操做和其餘讀寫操做之間發生指令重排,Release屏障能夠禁止寫操做和其餘讀寫操做之間發生指令重排。
那sychronized是如何實現有序性的呢,其實就是利用了這兩個內存屏障。以下:
sychronized(this){ // monitorenter // Load內存屏障 // Acquire內存屏障 //... //Release內存屏障 } //monitorexit //Store內存屏障
須要注意的是Acquire屏障和Release屏障保證的是sychronized內部的代碼不會與外部的代碼之間發生指令重排,內部的代碼本身仍是可能發生指令重排的。
jdk1.6後jvm對sychronized進行了鎖優化,這部分咱們作個概念瞭解就能夠了。
1.鎖消除
鎖消除是JIT編譯器對sychronized的優化,在編譯的時候會經過逃逸分析技術,來分析鎖對象。若是隻有一個線程來加鎖和解鎖,沒有鎖競爭,那就沒有必要加鎖,會去掉monitorenter和monitorexit指令。
2.鎖粗化
這個意思是,若是有多個連續的加鎖釋放鎖操做,那麼編譯後會變成一把鎖。
例如
sychronized(this){}
sychronized(this){}
sychronized(this){}
連着三個加鎖操做,編譯後會變成一個。
3.偏向鎖
偏向鎖主要是爲了減小monitorenter和monitorexit指令的CAS操做,減小開銷,若是認爲當前鎖大機率只有一個線程來競爭,那麼就會給這個鎖維護好一個偏好Bias,以後該線程加鎖和釋放鎖都經過這個Bias來執行,不須要去執行CAS了。
可是若是發現有其餘線程來競爭鎖,就會收回以前分配好的偏好。
4.輕量級鎖
若是偏向鎖沒能實現,也就是說有多個線程競爭鎖,那麼就會採用輕量級鎖。
其實就是將對象裏的輕量級鎖指針指向一個已經獲取了鎖的線程,而後判斷一下是否是本身加的鎖,若是是就直接執行,若是不是說明有其餘線程加了鎖,就會升級爲重量級鎖,重量級鎖流程咱們上文中介紹原子性的時候已經說過了。
5.適應性自旋鎖
在許多場景中,同步資源的鎖定時間很短,爲了這一小段時間去切換線程,線程掛起和恢復的花費可能會讓系統得不償失。爲了讓當前線程「稍等一下」,咱們需讓當前線程進行自旋。
若是在自旋完成後前面鎖同步資源的線程已經釋放了鎖,那麼當前線程就能夠沒必要阻塞而是直接獲取同步資源,從而避免了切換線程的開銷。這就是自旋鎖。
適應性自旋鎖意味着自旋的時間(次數)再也不固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。
到這裏,有關synchronized的底層實現咱們基本上已經聊完了。
使用鎖來保證原子性,使用內存屏障來保證可見性和有序性。
同時jvm又對sychronized作了一些優化。
相信小夥伴們理解了本文的內容,會收穫頗豐。
那咱們下次再見。
往期文章推薦: