一、回顧synchronized關(guān)鍵字
synchronized關(guān)鍵字有個(gè)名字,叫做內(nèi)置鎖。
為什么有了synchronized關(guān)鍵字還有個(gè)顯式鎖呢?
synchronized的形式很固化,因?yàn)樗仨氁玫芥i然后再釋放鎖,其他拿不到鎖的線程就處于一個(gè)阻塞狀態(tài),我們?nèi)绻L試中斷這個(gè)線程是中斷不了的。換句話說(shuō),線程必須要拿到synchronized鎖為止,拿不到鎖我就不給你中斷。
原因:
顯示鎖主要是有一個(gè)Lock接口,Lock中有一個(gè)方法lockInterruptibly()可以讓線程可中斷地拿鎖,當(dāng)線程處于等待拿鎖的狀態(tài)的時(shí)候,我們可以在外面通知它不用去拿鎖了,你可以去做別的事情。還有個(gè)tryLock方法可以進(jìn)行嘗試獲取鎖,拿不到馬上退出等待下一次機(jī)會(huì),嘗試拿鎖。

排他鎖:
不管是synchronized關(guān)鍵字還是ReentrantLock,都是一種排他鎖。
排他鎖的意思是:只有拿到鎖的線程可以進(jìn)行業(yè)務(wù)邏輯的推進(jìn),其他線程只能乖乖等著。
二、synchronized關(guān)鍵字和顯示鎖該用誰(shuí)?
一般情況下使用synchronized關(guān)鍵字。除非我們需要超時(shí)獲取、非阻塞獲取、中斷獲取的情況下才使用Lock接口,因?yàn)閟ynchronized是JDK為我們提供的內(nèi)置的語(yǔ)言層面的一種鎖,不管從性能還是JDK內(nèi)部?jī)?yōu)化的力度來(lái)看都要比Lock好,而且Lock是一個(gè)類,使用它的話必須要進(jìn)行實(shí)例化,不可避免的就需要占據(jù)Java內(nèi)存資源。
三、使用Lock
1.標(biāo)準(zhǔn)用法**
lock.lock();
try{
//do my work
}finally{
//一定要把釋放鎖的動(dòng)作放在finally子塊里面
lock.unlock();
}
2.實(shí)現(xiàn)Lock接口的類

1>.可重入鎖ReentrantLock
在多線程遞歸過(guò)程中,有這么一種場(chǎng)景:A線程進(jìn)入了X遞歸的代碼塊,拿到了鎖,在進(jìn)一步遞歸的時(shí)候又來(lái)到了X遞歸代碼塊,但是這個(gè)時(shí)候當(dāng)前線程已經(jīng)持有了鎖。synchronized在設(shè)計(jì)的時(shí)候已經(jīng)考慮到了這個(gè)問(wèn)題,所以synchronized支持可重入。ReentrantLock就是為了支持這一場(chǎng)景所實(shí)現(xiàn)的,你進(jìn)入這個(gè)方法幾次就獲取幾次,進(jìn)而需要釋放幾次。
在可重入鎖中,有一個(gè)概念,叫“鎖的公平和非公平”。
公平鎖:
拿鎖的時(shí)候,線程A先獲得鎖,我一定先被滿足。等待時(shí)間最長(zhǎng)的線程,一定先獲取鎖。
非公平鎖:
即使我先請(qǐng)求獲取鎖,但是我拿鎖的請(qǐng)求可能被后滿足
效率?
非公平鎖的效率比公平鎖高
為什么?
拿鎖是基于阻塞的方式,當(dāng)一個(gè)線程在拿鎖的過(guò)程中,拿不到鎖就被操作系統(tǒng)給掛起來(lái),當(dāng)前拿到鎖的線程處理完畢之后,操作系統(tǒng)需要將等待拿鎖的線程喚醒起,這個(gè)過(guò)程需要大量的上下文切換,而且線程越多這個(gè)過(guò)程就越頻繁,因此對(duì)于公平鎖而言,等待的線程越多,切換上下文的時(shí)間越長(zhǎng),效率越慢。而對(duì)于非公平鎖,是搶占式的,剛進(jìn)來(lái)的線程一直處于可運(yùn)行狀態(tài)就沒(méi)有上下文切換的時(shí)間,因此效率高。
如何實(shí)現(xiàn)非公平和公平?
在ReentrantLock的構(gòu)造方法中有一個(gè)布爾值fair,如果是缺省的話,就是一個(gè)非公平鎖,如果想讓它變得公平,就傳入一個(gè)true即可。

2>.可重入鎖ReentrantReadWriteLock
讀寫(xiě)鎖本質(zhì)上有兩把鎖:一把寫(xiě)鎖,一把讀鎖。當(dāng)線程。
當(dāng)有線程持有了讀鎖的時(shí)候,其他的線程可以繼續(xù)獲取讀鎖來(lái)進(jìn)行數(shù)據(jù)的讀取操作。讀鎖是可以共享的。
當(dāng)有線程持有了寫(xiě)鎖的時(shí)候,其他的線程不管是讀還是寫(xiě)都不能進(jìn)行。寫(xiě)鎖時(shí)一種排他鎖。
當(dāng)有讀多寫(xiě)少的需求的時(shí)候,使用
ReentrantReadWriteLock對(duì)性能有極大的提升。當(dāng)有讀多寫(xiě)一的需求的時(shí)候,使用
volatile關(guān)鍵字對(duì)性能有極大的提升。JDK1.8改進(jìn)
在JDK1.8里面對(duì)讀寫(xiě)鎖又做了進(jìn)一步的改進(jìn),提出了StampedLock,移步:https://blog.csdn.net/sunfeizhi/article/details/52135136
3>.Condition接口
我們?cè)趕ynchronized關(guān)鍵字中,如果要進(jìn)行多線程協(xié)作,會(huì)用到notify和wait方法。如果要在顯式鎖方面用通知,JDK為我們提供了Condition接口。
Condition接口提供的方法:

對(duì)于每個(gè)顯式鎖而言,他們內(nèi)部都有一個(gè)conditioin,通過(guò)
Lock.newCondition();去獲取。lock和Condition協(xié)調(diào)
應(yīng)該使用Condition的signal()方法而不應(yīng)該去使用signalAll()方法,因?yàn)镃ondition鎖的時(shí)候是對(duì)于特定的顯式鎖去綁定的,通知也只會(huì)通知與綁定的鎖有關(guān)的線程。
四、顯示鎖底層構(gòu)建AQS實(shí)現(xiàn)思想--CLH隊(duì)列鎖

AQS不僅僅在Java語(yǔ)言層面用到了,在很多語(yǔ)言中也用到了這個(gè)思想。CLH隊(duì)列鎖是三個(gè)開(kāi)發(fā)者的名字的開(kāi)頭,是基于鏈表的、可擴(kuò)展的、高性能的、公平的自旋鎖。當(dāng)一個(gè)線程A需要去獲取鎖的時(shí)候,需要構(gòu)造一個(gè)QNode節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu),QNode里面有兩個(gè)變量,一個(gè)myPred,一個(gè)locked;locked是個(gè)布爾值,當(dāng)locked設(shè)置為true的時(shí)候就獲取到了鎖。myPred指向當(dāng)前線程的前驅(qū)節(jié)點(diǎn)。當(dāng)一個(gè)線程獲取鎖之后,locked變?yōu)閠rue,然后將自己添加到CLH隊(duì)列鎖的尾部,把自己的前驅(qū)屬性指向自己的前驅(qū)節(jié)點(diǎn)。當(dāng)下一個(gè)線程B想要獲取鎖的時(shí)候,同理將線程B添加到隊(duì)列鎖的尾部,然后不停地去自旋,檢測(cè)它的前一個(gè)節(jié)點(diǎn)A有沒(méi)有釋放鎖,釋放鎖的標(biāo)志就是locked成員變量有沒(méi)有變成false,變成了false之后,B節(jié)點(diǎn)就將前驅(qū)節(jié)點(diǎn)進(jìn)行釋放,然后將自己的locked屬性設(shè)置為true獲取到鎖,進(jìn)行自己的業(yè)務(wù)操作。
計(jì)算機(jī)體系結(jié)構(gòu)中的SMP(對(duì)稱多處理器)都是基于CLH隊(duì)列鎖的思想實(shí)現(xiàn)的。