摘要:為了拓展同步代碼塊中的監(jiān)視器鎖,開(kāi)始,出現(xiàn)了接口,它實(shí)現(xiàn)了可定時(shí)可輪詢與可中斷的鎖獲取操作,公平隊(duì)列,以及非塊結(jié)構(gòu)的鎖。
前言
系列文章目錄
前面幾篇我們學(xué)習(xí)了synchronized同步代碼塊,了解了java的內(nèi)置鎖,并學(xué)習(xí)了監(jiān)視器鎖的wait/notify機(jī)制。在大多數(shù)情況下,內(nèi)置鎖都能很好的工作,但它在功能上存在一些局限性,例如無(wú)法實(shí)現(xiàn)非阻塞結(jié)構(gòu)的加鎖規(guī)則等。為了拓展同步代碼塊中的監(jiān)視器鎖,java 1.5 開(kāi)始,出現(xiàn)了lock接口,它實(shí)現(xiàn)了可定時(shí)、可輪詢與可中斷的鎖獲取操作,公平隊(duì)列,以及非塊結(jié)構(gòu)的鎖。
與內(nèi)置鎖不同,Lock是一種顯式鎖,它更加“危險(xiǎn)”,因?yàn)樵诔绦螂x開(kāi)被鎖保護(hù)的代碼塊時(shí),不會(huì)像監(jiān)視器鎖那樣自動(dòng)釋放,需要我們手動(dòng)釋放鎖。所以,在我們使用lock鎖時(shí),一定要記得:
在finally塊中調(diào)用lock.unlock()手動(dòng)釋放鎖!??!
在finally塊中調(diào)用lock.unlock()手動(dòng)釋放鎖!?。?/em>
在finally塊中調(diào)用lock.unlock()手動(dòng)釋放鎖?。?!
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
典型的使用方式:
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }鎖的獲取
Lock接口定義了四種獲取鎖的方式,下面我們一個(gè)個(gè)來(lái)看
lock()
阻塞式獲取,在沒(méi)有獲取到鎖時(shí),當(dāng)前線程將會(huì)休眠,不會(huì)參與線程調(diào)度,直到獲取到鎖為止,獲取鎖的過(guò)程中不響應(yīng)中斷。
lockInterruptibly()
阻塞式獲取,并且可中斷,該方法將在以下兩種情況之一發(fā)生的情況下拋出InterruptedException
在調(diào)用該方法時(shí),線程的中斷標(biāo)志位已經(jīng)被設(shè)為true了
在獲取鎖的過(guò)程中,線程被中斷了,并且鎖的獲取實(shí)現(xiàn)會(huì)響應(yīng)這個(gè)中斷
在InterruptedException拋出后,當(dāng)前線程的中斷標(biāo)志位將會(huì)被清除
tryLock()
非阻塞式獲取,從名字中也可以看出,try就是試一試的意思,無(wú)論成功與否,該方法都是立即返回的
相比前面兩種阻塞式獲取的方式,該方法是有返回值的,獲取鎖成功了則返回true,獲取鎖失敗了則返回false
tryLock(long time, TimeUnit unit)
帶超時(shí)機(jī)制,并且可中斷
如果可以獲取帶鎖,則立即返回true
如果獲取不到鎖,則當(dāng)前線程將會(huì)休眠,不會(huì)參與線程調(diào)度,直到以下三個(gè)條件之一被滿足:
當(dāng)前線程獲取到了鎖
其它線程中斷了當(dāng)前線程
設(shè)定的超時(shí)時(shí)間到了
該方法將在以下兩種情況之一發(fā)生的情況下拋出InterruptedException
在調(diào)用該方法時(shí),線程的中斷標(biāo)志位已經(jīng)被設(shè)為true了
在獲取鎖的過(guò)程中,線程被中斷了,并且鎖的獲取實(shí)現(xiàn)會(huì)響應(yīng)這個(gè)中斷
在InterruptedException拋出后,當(dāng)前線程的中斷標(biāo)志位將會(huì)被清除
如果超時(shí)時(shí)間到了,當(dāng)前線程還沒(méi)有獲得鎖,則會(huì)直接返回false(注意,這里并沒(méi)有拋出超時(shí)異常)
其實(shí),tryLock(long time, TimeUnit unit)更像是阻塞式與非阻塞式的結(jié)合體,即在一定條件下(超時(shí)時(shí)間內(nèi),沒(méi)有中斷發(fā)生)阻塞,不滿足這個(gè)條件則立即返回(非阻塞)。
這里把四種鎖的獲取方式總結(jié)如下:
相對(duì)于鎖的獲取,鎖的釋放的方法就簡(jiǎn)單的多,只有一個(gè)
void unlock();
值得注意的是,只有擁有的鎖的線程才能釋放鎖,并且,必須顯式地釋放鎖,這一點(diǎn)和離開(kāi)同步代碼塊就自動(dòng)被釋放的監(jiān)視器鎖是不同的。
newConditionLock接口還定義了一個(gè)newCondition方法:
Condition newCondition();
該方法將創(chuàng)建一個(gè)綁定在當(dāng)前Lock對(duì)象上的Condition對(duì)象,這說(shuō)明Condition對(duì)象和Lock對(duì)象是對(duì)應(yīng)的,一個(gè)Lock對(duì)象可以創(chuàng)建多個(gè)Condition對(duì)象,它們是一個(gè)對(duì)多的關(guān)系。
Condition 接口上面我們說(shuō)道,Lock接口中定義了newCondition方法,它返回一個(gè)關(guān)聯(lián)在當(dāng)前Lock對(duì)象上的Condition對(duì)象,下面我們來(lái)看看這個(gè)Condition對(duì)象是個(gè)啥。
每一個(gè)新工具的出現(xiàn)總是為了解決一定的問(wèn)題,Condition接口的出現(xiàn)也不例外。
如果說(shuō)Lock接口的出現(xiàn)是為了拓展現(xiàn)有的監(jiān)視器鎖,那么Condition接口的出現(xiàn)就是為了拓展同步代碼塊中的wait, notify機(jī)制。
通常情況下,我們調(diào)用wait方法,主要是因?yàn)橐欢ǖ臈l件沒(méi)有滿足,我們把需要滿足的事件或條件稱作條件謂詞。
而另一方面,由前面幾篇介紹synchronized原理的文章我們知道,所有調(diào)用了wait方法的線程,都會(huì)在同一個(gè)監(jiān)視器鎖的wait set中等待,這看上去很合理,但是卻是該機(jī)制的短板所在——所有的線程都等待在同一個(gè)notify方法上(notify方法指notify()和notifyAll()兩個(gè)方法,下同)。每一個(gè)調(diào)用wait方法的線程可能等待在不同的條件謂詞上,但是有時(shí)候即使自己等待的條件并沒(méi)有滿足,線程也有可能被“別的線程的”notify方法喚醒,因?yàn)榇蠹矣玫氖峭粋€(gè)監(jiān)視器鎖。這就好比一個(gè)班上有幾個(gè)重名的同學(xué)(使用相同的監(jiān)視器鎖),老師喊了這個(gè)名字(notify方法),結(jié)果這幾個(gè)同學(xué)全都站起來(lái)了(等待在監(jiān)視器鎖上的線程都被喚醒了)。
這樣以來(lái),即使自己被喚醒后,搶到了監(jiān)視器鎖,發(fā)現(xiàn)其實(shí)條件還是不滿足,還是得調(diào)用wait方法掛起,就導(dǎo)致了很多無(wú)意義的時(shí)間和CPU資源的浪費(fèi)。
這一切的根源就在于我們?cè)谡{(diào)用wait方法時(shí)沒(méi)有辦法來(lái)指明究竟是在等待什么樣的條件謂詞上,因此喚醒時(shí),也不知道該喚醒誰(shuí),只能把所有的線程都喚醒了。
因此,最好的方式是,我們?cè)趻炱饡r(shí)就指明了在什么樣的條件謂詞上掛起,同時(shí),在等待的事件發(fā)生后,只喚醒等待在這個(gè)事件上的線程,而實(shí)現(xiàn)了這個(gè)思路的就是Condition接口。
有了Condition接口,我們就可以在同一個(gè)鎖上創(chuàng)建不同的喚醒條件,從而在一定條件謂詞滿足后,有針對(duì)性的喚醒特定的線程,而不是一股腦的將所有等待的線程都喚醒。
Condition的 await/signal 機(jī)制既然前面說(shuō)了Condition接口的出現(xiàn)是為了拓展現(xiàn)有的wait/notify機(jī)制,那我們就先來(lái)看看現(xiàn)有的wait/notify機(jī)制有哪些方法:
public class Object { public final void wait() throws InterruptedException { wait(0); } public final native void wait(long timeout) throws InterruptedException; public final void wait(long timeout, int nanos) throws InterruptedException { // 這里省略方法的實(shí)現(xiàn) } public final native void notify(); public final native void notifyAll(); }
接下來(lái)我們?cè)倏纯碈ondition接口有哪些方法:
public interface Condition { void await() throws InterruptedException; long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; void awaitUninterruptibly(); boolean awaitUntil(Date deadline) throws InterruptedException; void signal(); void signalAll(); }
對(duì)比發(fā)現(xiàn),這里存在明顯的對(duì)應(yīng)關(guān)系:
Object 方法 | Condition 方法 | 區(qū)別 |
---|---|---|
void wait() | void await() | |
void wait(long timeout) | long awaitNanos(long nanosTimeout) | 時(shí)間單位,返回值 |
void wait(long timeout, int nanos) | boolean await(long time, TimeUnit unit) | 時(shí)間單位,參數(shù)類型,返回值 |
void notify() | void signal() | |
void notifyAll() | void signalAll() | |
- | void awaitUninterruptibly() | Condition獨(dú)有 |
- | boolean awaitUntil(Date deadline) | Condition獨(dú)有 |
它們?cè)诮涌诘囊?guī)范上都是差不多的,只不過(guò)wait/notify機(jī)制針對(duì)的是所有在監(jiān)視器鎖的wait set中的線程,而await/signal機(jī)制針對(duì)的是所有等待在該Condition上的線程。
這里多說(shuō)一句,在接口的規(guī)范中,wait(long timeout)的時(shí)間單位是毫秒(milliseconds), 而awaitNanos(long nanosTimeout)的時(shí)間單位是納秒(nanoseconds), 就這一點(diǎn)而言,awaitNanos這個(gè)方法名其實(shí)語(yǔ)義上更清晰,并且相對(duì)于wait(long timeout, int nanos)這個(gè)略顯雞肋的方法(之前的分析中我們已經(jīng)吐槽過(guò)這個(gè)方法的實(shí)現(xiàn)了),await(long time, TimeUnit unit)這個(gè)方法就顯得更加直觀和有效。
另外一點(diǎn)值得注意的是,awaitNanos(long nanosTimeout)是有返回值的,它返回了剩余等待的時(shí)間;await(long time, TimeUnit unit)也是有返回值的,如果該方法是因?yàn)槌瑫r(shí)時(shí)間到了而返回的,則該方法返回false, 否則返回true。
大家有沒(méi)有覺(jué)的奇怪,同樣是帶超時(shí)時(shí)間的等待,為什么wait方式?jīng)]有返回值,await方式有返回值呢。
存在即合理,既然多加了返回值,自然是有它的用意,那么這個(gè)多加的返回值有什么用呢?
我們知道,當(dāng)一個(gè)線程從帶有超時(shí)時(shí)間的wait/await方法返回時(shí),必然是發(fā)生了以下4種情況之一:
其他線程調(diào)用了notify/signal方法,并且當(dāng)前線程恰好是被選中來(lái)喚醒的那一個(gè)
其他線程調(diào)用了notifyAll/signalAll方法
其他線程中斷了當(dāng)前線程
超時(shí)時(shí)間到了
其中,第三條會(huì)拋出InterruptedException,是比較容易分辨的;除去這個(gè),當(dāng)wait方法返回后,我們其實(shí)無(wú)法區(qū)分它是因?yàn)槌瑫r(shí)時(shí)間到了返回了,還是被notify返回的。但是對(duì)于await方法,因?yàn)樗怯蟹祷刂档?,我們就能夠通過(guò)返回值來(lái)區(qū)分:
如果awaitNanos(long nanosTimeout)的返回值大于0,說(shuō)明超時(shí)時(shí)間還沒(méi)到,則該返回是由signal行為導(dǎo)致的
如果await(long time, TimeUnit unit)返回true, 說(shuō)明超時(shí)時(shí)間還沒(méi)到,則該返回是由signal行為導(dǎo)致的
源碼的注釋也說(shuō)了,await(long time, TimeUnit unit)相當(dāng)于調(diào)用awaitNanos(unit.toNanos(time)) > 0
所以,它們的返回值能夠幫助我們弄清楚方法返回的原因。
Condition接口中還有兩個(gè)在Object中找不到對(duì)應(yīng)的方法:
void awaitUninterruptibly(); boolean awaitUntil(Date deadline) throws InterruptedException;
前面說(shuō)的所有的wait/await方法,它們方法的簽名中都拋出了InterruptedException,說(shuō)明他們?cè)诘却倪^(guò)程中都是響應(yīng)中斷的,awaitUninterruptibly方法從名字中就可以看出,它在等待鎖的過(guò)程中是不響應(yīng)中斷的,所以沒(méi)有InterruptedException拋出。也就是說(shuō),它會(huì)一直阻塞,直到signal/signalAll被調(diào)用。如果在這過(guò)程中線程被中斷了,它并不響應(yīng)這個(gè)中斷,只是在該方法返回的時(shí)候,該線程的中斷標(biāo)志位將是true, 調(diào)用者可以檢測(cè)這個(gè)中斷標(biāo)志位以輔助判斷在等待過(guò)程中是否發(fā)生了中斷,以此決定要不要做額外的處理。
boolean awaitUntil(Date deadline)和boolean await(long time, TimeUnit unit) 其實(shí)作用是差不多的,返回值代表的含義也一樣,只不過(guò)一個(gè)是相對(duì)時(shí)間,一個(gè)是絕對(duì)時(shí)間,awaitUntil方法的參數(shù)是Date,表示了一個(gè)絕對(duì)的時(shí)間,即截止日期,在這個(gè)日期之前,該方法會(huì)一直等待,除非被signal或者被中斷。
至此,Lock接口和Condition接口我們就分析完了。
我們將在下一篇中給出Lock接口的具體實(shí)現(xiàn)的例子,在逐行分析AQS源碼(4)——Condition接口實(shí)現(xiàn)中給出Condition接口具體實(shí)現(xiàn)的例子。
(完)
系列文章目錄
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/77184.html
摘要:為了避免一篇文章的篇幅過(guò)長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開(kāi)始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問(wèn)題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...
摘要:為了避免一篇文章的篇幅過(guò)長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開(kāi)始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問(wèn)題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...
摘要:前言本篇文章是基于線程間的同步與通信和這篇文章寫的,在那篇文章中,我們分析了接口所定義的方法,本篇我們就來(lái)看看對(duì)于接口的這些接口方法的具體實(shí)現(xiàn)。因此,條件隊(duì)列在出隊(duì)時(shí),線程并不持有鎖。 前言 本篇文章是基于線程間的同步與通信(4)——Lock 和 Condtion 這篇文章寫的,在那篇文章中,我們分析了Condition接口所定義的方法,本篇我們就來(lái)看看AQS對(duì)于Condition接口...
摘要:例如,線程需要互相等待,保證所有線程都執(zhí)行完了之后才能一起通過(guò)。獲取正在等待中的線程數(shù)注意,這里加了鎖,因?yàn)榉椒赡軙?huì)被多個(gè)線程同時(shí)修改。只要有一行沒(méi)有處理完,所有的線程都會(huì)在處等待,最后一個(gè)執(zhí)行完的線程將會(huì)負(fù)責(zé)喚醒所有等待的線程 前言 系列文章目錄 上一篇 我們學(xué)習(xí)了基于AQS共享鎖實(shí)現(xiàn)的CountDownLatch,本篇我們來(lái)看看另一個(gè)和它比較像的并發(fā)工具CyclicBarrier...
摘要:由此可見(jiàn),自旋鎖和各有優(yōu)劣,他們分別適用于競(jìng)爭(zhēng)不多和競(jìng)爭(zhēng)激烈的場(chǎng)景中。每一個(gè)試圖進(jìn)入同步代碼塊的線程都會(huì)被封裝成對(duì)象,它們或在對(duì)象的中,或在中,等待成為對(duì)象的成為的對(duì)象即獲取了監(jiān)視器鎖。 前言 系列文章目錄 前面兩篇文章我們介紹了synchronized同步代碼塊以及wait和notify機(jī)制,大致知道了這些關(guān)鍵字和方法是干什么的,以及怎么用。 但是,知其然,并不知其所以然。 例如...
閱讀 1009·2021-09-09 09:32
閱讀 3019·2021-09-02 10:20
閱讀 2833·2021-07-23 11:24
閱讀 904·2019-08-30 15:54
閱讀 3725·2019-08-30 15:54
閱讀 1419·2019-08-30 11:02
閱讀 2916·2019-08-26 17:40
閱讀 1193·2019-08-26 13:55