Java中的锁[原理、锁优化、CAS、AQS]
??點(diǎn)擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號(hào)
重磅資訊、干貨,第一時(shí)間送達(dá) 今日推薦:用好Java中的枚舉,真的沒(méi)有那么簡(jiǎn)單!個(gè)人原創(chuàng)+1博客:點(diǎn)擊前往,查看更多 作者:高廣超 鏈接:https://www.jianshu.com/p/e674ee68fd3f1、為什么要用鎖?
鎖-是為了解決并發(fā)操作引起的臟讀、數(shù)據(jù)不一致的問(wèn)題。
2、鎖實(shí)現(xiàn)的基本原理
2.1、volatile
★Java編程語(yǔ)言允許線(xiàn)程訪(fǎng)問(wèn)共享變量, 為了確保共享變量能被準(zhǔn)確和一致地更新,線(xiàn)程應(yīng)該確保通過(guò)排他鎖單獨(dú)獲得這個(gè)變量。Java語(yǔ)言提供了volatile,在某些情況下比鎖要更加方便。
volatile在多處理器開(kāi)發(fā)中保證了共享變量的“ 可見(jiàn)性”。可見(jiàn)性的意思是當(dāng)一個(gè)線(xiàn)程修改一個(gè)共享變量時(shí),另外一個(gè)線(xiàn)程能讀到這個(gè)修改的值。
”image.png
結(jié)論:如果volatile變量修飾符使用恰當(dāng)?shù)脑?huà),它比synchronized的使用和執(zhí)行成本更低,因?yàn)樗粫?huì)引起線(xiàn)程上下文的切換和調(diào)度。
2.2、synchronized
★synchronized通過(guò)鎖機(jī)制實(shí)現(xiàn)同步。
”先來(lái)看下利用synchronized實(shí)現(xiàn)同步的基礎(chǔ):Java中的每一個(gè)對(duì)象都可以作為鎖。
具體表現(xiàn)為以下3種形式。
對(duì)于普通同步方法,鎖是當(dāng)前實(shí)例對(duì)象。
對(duì)于靜態(tài)同步方法,鎖是當(dāng)前類(lèi)的Class對(duì)象。
對(duì)于同步方法塊,鎖是Synchonized括號(hào)里配置的對(duì)象。
當(dāng)一個(gè)線(xiàn)程試圖訪(fǎng)問(wèn)同步代碼塊時(shí),它首先必須得到鎖,退出或拋出異常時(shí)必須釋放鎖。
2.2.1 synchronized實(shí)現(xiàn)原理
★synchronized是基于Monitor來(lái)實(shí)現(xiàn)同步的。
”Monitor從兩個(gè)方面來(lái)支持線(xiàn)程之間的同步:
互斥執(zhí)行
協(xié)作
1、Java 使用對(duì)象鎖 ( 使用 synchronized 獲得對(duì)象鎖 ) 保證工作在共享的數(shù)據(jù)集上的線(xiàn)程互斥執(zhí)行。
2、使用 notify/notifyAll/wait 方法來(lái)協(xié)同不同線(xiàn)程之間的工作。
3、Class和Object都關(guān)聯(lián)了一個(gè)Monitor。
Monitor 的工作機(jī)理
線(xiàn)程進(jìn)入同步方法中。
為了繼續(xù)執(zhí)行臨界區(qū)代碼,線(xiàn)程必須獲取 Monitor 鎖。如果獲取鎖成功,將成為該監(jiān)視者對(duì)象的擁有者。任一時(shí)刻內(nèi),監(jiān)視者對(duì)象只屬于一個(gè)活動(dòng)線(xiàn)程(The Owner)
擁有監(jiān)視者對(duì)象的線(xiàn)程可以調(diào)用 wait() 進(jìn)入等待集合(Wait Set),同時(shí)釋放監(jiān)視鎖,進(jìn)入等待狀態(tài)。
其他線(xiàn)程調(diào)用 notify() / notifyAll() 接口喚醒等待集合中的線(xiàn)程,這些等待的線(xiàn)程需要重新獲取監(jiān)視鎖后才能執(zhí)行 wait() 之后的代碼。
同步方法執(zhí)行完畢了,線(xiàn)程退出臨界區(qū),并釋放監(jiān)視鎖。
參考文檔:https://www.ibm.com/developerworks/cn/java/j-lo-synchronized
2.2.2 synchronized具體實(shí)現(xiàn)
1、同步代碼塊采用monitorenter、monitorexit指令顯式的實(shí)現(xiàn)。
2、同步方法則使用ACC_SYNCHRONIZED標(biāo)記符隱式的實(shí)現(xiàn)。
通過(guò)實(shí)例來(lái)看看具體實(shí)現(xiàn):
public class SynchronizedTest {public synchronized void method1(){System.out.println("Hello World!");}public void method2(){synchronized (this){System.out.println("Hello World!");}} }javap編譯后的字節(jié)碼如下:
image.png
monitorenter
每一個(gè)對(duì)象都有一個(gè)monitor,一個(gè)monitor只能被一個(gè)線(xiàn)程擁有。當(dāng)一個(gè)線(xiàn)程執(zhí)行到monitorenter指令時(shí)會(huì)嘗試獲取相應(yīng)對(duì)象的monitor,獲取規(guī)則如下:
如果monitor的進(jìn)入數(shù)為0,則該線(xiàn)程可以進(jìn)入monitor,并將monitor進(jìn)入數(shù)設(shè)置為1,該線(xiàn)程即為monitor的擁有者。
如果當(dāng)前線(xiàn)程已經(jīng)擁有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1,所以synchronized關(guān)鍵字實(shí)現(xiàn)的鎖是可重入的鎖。
如果monitor已被其他線(xiàn)程擁有,則當(dāng)前線(xiàn)程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor。
monitorexit
只有擁有相應(yīng)對(duì)象的monitor的線(xiàn)程才能執(zhí)行monitorexit指令。每執(zhí)行一次該指令monitor進(jìn)入數(shù)減1,當(dāng)進(jìn)入數(shù)為0時(shí)當(dāng)前線(xiàn)程釋放monitor,此時(shí)其他阻塞的線(xiàn)程將可以嘗試獲取該monitor。
2.2.3 鎖存放的位置
鎖標(biāo)記存放在Java對(duì)象頭的Mark Word中。
Java對(duì)象頭長(zhǎng)度
32位JVM Mark Word 結(jié)構(gòu)
32位JVM Mark Word 狀態(tài)變化
64位JVM Mark Word 結(jié)構(gòu)
2.2.3 synchronized的鎖優(yōu)化
JavaSE1.6為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”。
在JavaSE1.6中,鎖一共有4種狀態(tài),級(jí)別從低到高依次是:無(wú)鎖狀態(tài)、偏向鎖狀態(tài)、輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),這幾個(gè)狀態(tài)會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。
鎖可以升級(jí)但不能降級(jí),意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖。這種鎖升級(jí)卻不能降級(jí)的策略,目的是為了提高獲得鎖和釋放鎖的效率。
偏向鎖:
★無(wú)鎖競(jìng)爭(zhēng)的情況下為了減少鎖競(jìng)爭(zhēng)的資源開(kāi)銷(xiāo),引入偏向鎖。
”image.png
輕量級(jí)鎖:
★輕量級(jí)鎖所適應(yīng)的場(chǎng)景是線(xiàn)程交替執(zhí)行同步塊的情況。
”image.png
**鎖粗化(Lock Coarsening):**也就是減少不必要的緊連在一起的unlock,lock操作,將多個(gè)連續(xù)的鎖擴(kuò)展成一個(gè)范圍更大的鎖。
**鎖消除(Lock Elimination):**鎖削除是指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí),對(duì)一些代碼上要求同步,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行削除。
**適應(yīng)性自旋(Adaptive Spinning):**自適應(yīng)意味著自旋的時(shí)間不再固定了,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定。如果在同一個(gè)鎖對(duì)象上,自旋等待剛剛成功獲得過(guò)鎖,并且持有鎖的線(xiàn)程正在運(yùn)行中,那么虛擬機(jī)就會(huì)認(rèn)為這次自旋也很有可能再次成功,進(jìn)而它將允許自旋等待持續(xù)相對(duì)更長(zhǎng)的時(shí)間,比如100個(gè)循環(huán)。另一方面,如果對(duì)于某個(gè)鎖,自旋很少成功獲得過(guò),那在以后要獲取這個(gè)鎖時(shí)將可能省略掉自旋過(guò)程,以避免浪費(fèi)處理器資源。
2.2.4 鎖的優(yōu)缺點(diǎn)對(duì)比
image.png
2.3、CAS
★CAS,在Java并發(fā)應(yīng)用中通常指CompareAndSwap或CompareAndSet,即比較并交換。
”1、CAS是一個(gè)原子操作,它比較一個(gè)內(nèi)存位置的值并且只有相等時(shí)修改這個(gè)內(nèi)存位置的值為新的值,保證了新的值總是基于最新的信息計(jì)算的,如果有其他線(xiàn)程在這期間修改了這個(gè)值則CAS失敗。CAS返回是否成功或者內(nèi)存位置原來(lái)的值用于判斷是否CAS成功。
2、JVM中的CAS操作是利用了處理器提供的CMPXCHG指令實(shí)現(xiàn)的。
優(yōu)點(diǎn):
競(jìng)爭(zhēng)不大的時(shí)候系統(tǒng)開(kāi)銷(xiāo)小。
缺點(diǎn):
循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大。
ABA問(wèn)題。
只能保證一個(gè)共享變量的原子操作。
3、Java中的鎖實(shí)現(xiàn)
3.1、隊(duì)列同步器(AQS)
★隊(duì)列同步器AbstractQueuedSynchronizer(以下簡(jiǎn)稱(chēng)同步器),是用來(lái)構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架。
”3.1.1、它使用了一個(gè)int成員變量表示同步狀態(tài)。
image.png
3.1.2、通過(guò)內(nèi)置的FIFO雙向隊(duì)列來(lái)完成獲取鎖線(xiàn)程的排隊(duì)工作。
同步器包含兩個(gè)節(jié)點(diǎn)類(lèi)型的應(yīng)用,一個(gè)指向頭節(jié)點(diǎn),一個(gè)指向尾節(jié)點(diǎn),未獲取到鎖的線(xiàn)程會(huì)創(chuàng)建節(jié)點(diǎn)線(xiàn)程安全(compareAndSetTail)的加入隊(duì)列尾部。同步隊(duì)列遵循FIFO,首節(jié)點(diǎn)是獲取同步狀態(tài)成功的節(jié)點(diǎn)。
image.png
未獲取到鎖的線(xiàn)程將創(chuàng)建一個(gè)節(jié)點(diǎn),設(shè)置到尾節(jié)點(diǎn)。如下圖所示:
image.png
首節(jié)點(diǎn)的線(xiàn)程在釋放鎖時(shí),將會(huì)喚醒后繼節(jié)點(diǎn)。而后繼節(jié)點(diǎn)將會(huì)在獲取鎖成功時(shí)將自己設(shè)置為首節(jié)點(diǎn)。如下圖所示:
image.png
3.1.3、獨(dú)占式/共享式鎖獲取
★獨(dú)占式:有且只有一個(gè)線(xiàn)程能獲取到鎖,如:ReentrantLock。
共享式:可以多個(gè)線(xiàn)程同時(shí)獲取到鎖,如:CountDownLatch
”獨(dú)占式
每個(gè)節(jié)點(diǎn)自旋觀察自己的前一節(jié)點(diǎn)是不是Header節(jié)點(diǎn),如果是,就去嘗試獲取鎖。
image.png
獨(dú)占式鎖獲取流程:
image.png
共享式:
共享式與獨(dú)占式的區(qū)別:
image.png
共享鎖獲取流程:
image.png
4、鎖的使用用例
4.1、ConcurrentHashMap的實(shí)現(xiàn)原理及使用
ConcurrentHashMap類(lèi)圖
ConcurrentHashMap數(shù)據(jù)結(jié)構(gòu)
結(jié)論:ConcurrentHashMap使用的鎖分段技術(shù)。首先將數(shù)據(jù)分成一段一段地存儲(chǔ),然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個(gè)線(xiàn)程占用鎖訪(fǎng)問(wèn)其中一個(gè)段數(shù)據(jù)的時(shí)候,其他段的數(shù)據(jù)也能被其他線(xiàn)程訪(fǎng)問(wèn)。
總結(jié)
以上是生活随笔為你收集整理的Java中的锁[原理、锁优化、CAS、AQS]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: MySQL数据库备份之主从同步配置
- 下一篇: Java 效率工具之 Lombok