一陡鹃、回顧synchronized關(guān)鍵字
synchronized關(guān)鍵字有個(gè)名字,叫做內(nèi)置鎖抖坪。
為什么有了synchronized關(guān)鍵字還有個(gè)顯式鎖呢萍鲸?
synchronized的形式很固化,因?yàn)樗仨氁玫芥i然后再釋放鎖擦俐,其他拿不到鎖的線程就處于一個(gè)阻塞狀態(tài)脊阴,我們?nèi)绻L試中斷這個(gè)線程是中斷不了的。換句話說蚯瞧,線程必須要拿到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)鍵字和顯示鎖該用誰蓖救?
一般情況下使用synchronized關(guān)鍵字洪规。除非我們需要超時(shí)獲取、非阻塞獲取循捺、中斷獲取的情況下才使用Lock接口斩例,因?yàn)閟ynchronized是JDK為我們提供的內(nèi)置的語言層面的一種鎖,不管從性能還是JDK內(nèi)部?jī)?yōu)化的力度來看都要比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
在多線程遞歸過程中,有這么一種場(chǎng)景:A線程進(jìn)入了X遞歸的代碼塊踩萎,拿到了鎖正罢,在進(jìn)一步遞歸的時(shí)候又來到了X遞歸代碼塊,但是這個(gè)時(shí)候當(dāng)前線程已經(jīng)持有了鎖驻民。synchronized在設(shè)計(jì)的時(shí)候已經(jīng)考慮到了這個(gè)問題,所以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è)線程在拿鎖的過程中嬉橙,拿不到鎖就被操作系統(tǒng)給掛起來,當(dāng)前拿到鎖的線程處理完畢之后寥假,操作系統(tǒng)需要將等待拿鎖的線程喚醒起市框,這個(gè)過程需要大量的上下文切換,而且線程越多這個(gè)過程就越頻繁糕韧,因此對(duì)于公平鎖而言枫振,等待的線程越多喻圃,切換上下文的時(shí)間越長(zhǎng),效率越慢蒋得。而對(duì)于非公平鎖级及,是搶占式的,剛進(jìn)來的線程一直處于可運(yùn)行狀態(tài)就沒有上下文切換的時(shí)間额衙,因此效率高饮焦。
如何實(shí)現(xiàn)非公平和公平?
在ReentrantLock的構(gòu)造方法中有一個(gè)布爾值fair窍侧,如果是缺省的話县踢,就是一個(gè)非公平鎖,如果想讓它變得公平伟件,就傳入一個(gè)true即可硼啤。
2>.可重入鎖ReentrantReadWriteLock
讀寫鎖本質(zhì)上有兩把鎖:一把寫鎖,一把讀鎖斧账。當(dāng)線程谴返。
當(dāng)有線程持有了讀鎖的時(shí)候,其他的線程可以繼續(xù)獲取讀鎖來進(jìn)行數(shù)據(jù)的讀取操作咧织。讀鎖是可以共享的嗓袱。
當(dāng)有線程持有了寫鎖的時(shí)候,其他的線程不管是讀還是寫都不能進(jìn)行习绢。寫鎖時(shí)一種排他鎖渠抹。
當(dāng)有讀多寫少的需求的時(shí)候,使用
ReentrantReadWriteLock
對(duì)性能有極大的提升闪萄。當(dāng)有讀多寫一的需求的時(shí)候梧却,使用
volatile
關(guān)鍵字對(duì)性能有極大的提升。JDK1.8改進(jìn)
在JDK1.8里面對(duì)讀寫鎖又做了進(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,通過
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語言層面用到了狐胎,在很多語言中也用到了這個(gè)思想。CLH隊(duì)列鎖是三個(gè)開發(fā)者的名字的開頭歌馍,是基于鏈表的握巢、可擴(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有沒有釋放鎖,釋放鎖的標(biāo)志就是locked成員變量有沒有變成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)的命雀。