感謝網友【張超盟】的投稿
aqs(abstractqueuedsynchronizer)是 java.util.concurrent的基礎。j.u.c中宣傳的封裝良好的同步工具類semaphore、countdownlatch、reentrantlock、reentrantreadwritelock、futuretask等雖然各自都有不同特征,但是簡單看一下源碼,每個類内部都包含一個如下的内部類定義:
同時每個類内部都包含有這樣一個屬性,連屬性名都一樣!注釋已經暗示了,該類的同步機制正是通過這個aqs的子類來完成的。不得不感歎:“每個強大的同步工具類,内心都有一把同樣的鎖!”
幾種同步類提供的功能其實都是委托sync來完成。有些是部分功能,有些則是全部功能。 本文中就是想嘗試比較分析下在幾個同步工具類下面定義的aqs的子類如何來實作工具類要求的功能。當然包括兩部分,一部分是這些工具類如何使用其sync這種類型的同步器,也就是工具類向外提供的方法中,如何使用sync這個句柄;第二部分,就是工具類中自己定義的内部類sync繼承自aqs,那到底override了哪些方法來做到以父類aqs為基礎,提供受委托工具類的功能要求。
關于第一部分,sync如何被其工具類使用,請允許我無恥的在一個文章中把一個類所有代碼貼出來。
所幸方法很多,總的代碼行不多,因為每個方法都是一個風格,就是換個名直接調用sync的對應方法。這是semaphore中對sync的使用。是不是覺得寫這個代碼的作者比寫這個文章的作者還要無恥?在其他幾個工具類中,沒有這麼誇張,b但基本上也是這個風格,即以一個helper的方式向外面的封裝類提供功能支援。是以第一個問題,在文章中說到這裡,後面涉及到也隻會簡單描述。 主要是求索第二個問題,即每個工具類中自己定義的sync到底是什麼樣子,有哪些不同的特征,其實也就是代碼上看這些sync類對父類aqs做了哪些修改。
要介紹子類的特征,父類總得大緻介紹下。aqs的原理、設計等比較系統的東西,在這裡就不想涉及了。可以參照《深入淺出 java concurrency》系列的深入淺出 java concurrency (7): 鎖機制 part 2 aqs一節,謝謝這個系列,作者講的确實非常的深入淺出!要想了解更多,可以參考doug lea大師的原著the java.util.concurrent synchronizer framework。最簡單的辦法其實就是的耐心把abstractqueuedsynchronizer源碼前面注釋的javadoc完整的讀一遍就可以了。筆者反正有這樣的習慣。紮着腦袋看代碼,看注釋,然後自己看看是否能把一個package有個系統的視圖,如果需要再看相關的參考文檔來确認這個系統的視圖。
看一個對象有什麼本事,看他的構成是什麼樣,遠比看他由哪些行為來的要深遠。其實在oop這種以class方式承載功能的程式設計中,即看一個類包含的屬性,比他的方法也更容易了解對象的作用。看aqs類,暫時抛開outline視圖下需要兩屏才能看完的重要方法(還未展開conditionobject和node兩個重要的内部類),隻看該類包含的三個重要屬性的定義就能看出端倪。
注釋其實已經告訴我們了,node類型的head和tail是一個fifo的wait queue;一個int類型的狀态位state。到這裡也能猜到aqs對外呈現(或者說聲明)的主要行為就是由一個狀态位和一個有序隊列來配合完成。 最簡單的讀一下主要的四個方法:
分别對應鎖的擷取和釋放,隻是**shared字尾的表示一組表示共享鎖,而另外一組沒有字尾的表示排他鎖。隻用關注每個方法的第一行,都是這種try字型的風格:
即做一個判斷,然後做擷取或者釋放鎖。 其實aqs主要的工作思路正是如此:在擷取鎖時候,先判斷目前狀态是否允許擷取鎖,若是可以則擷取鎖,否則擷取不成功。擷取不成功則會阻塞,進入阻塞隊列。而釋放鎖時,一般會修改狀态位,喚醒隊列中的阻塞線程。 跟蹤這幾個try字型的方法定義,發現一個驚人的巧合,這幾個方法在aqs中居然都是一樣的定義:
即都是父類中隻有定義,在子類中實作。子類根據功能需要的不同,有選擇的對需要的方法進行實作。父類中提供一個執行模闆,但是具體步驟留給子類來定義,不同的子類有不同的實作。
簡單看下下面幾個方法的源碼發現定義中都涉及到了<code>getstate()</code>, <code>setstate(int)</code> <code>compareandsetstate(int, int),即對狀态位state的維護。</code>
<code>tryacquire(int)</code>
<code>tryrelease(int)</code>
<code>tryacquireshared(int)</code>
<code>tryreleaseshared(int)</code>
<code>下圖表示compareandsetstate(int, int)的調用,可以看的更清楚看到,說明幾個同步工具類内定義的sync類,即自定義子類中其實都涉及到對state的操作。</code>
而同時不小心觀察到aqs中有一大組final的方法,就是子類不能覆寫的,大緻看下方法内的定義,大部分都是直接或間接涉及對head和tail的操作,即對等待隊列的維護。
那在aqs的子類中有沒有對有序隊列的操作呢?檢索下對head和tail的引用即可找到結論。
對head的操作僅限于在aqs類内部,觀察方法的修飾,除了final就是private,即表示這些方法不可能被子類override,或者不可能在子類中直接被調用。看下圖對于tail的調用也是同樣的風格,即對等待隊列的操作全部不超過aqs類内部。
于是幾乎可以有這樣的結論:在aqs的設計中,在父類aqs中實作了對等待隊列的預設實作,無論是對共享鎖還是對排他鎖。子類中幾乎不用修改該部分功能,而state在子類中根據需要被賦予了不同的意義,子類通過對state的不同操作來提供不同的同步器功能,進而對封裝的工具類提供不同的功能。 在下面嘗試對以上觀點在aqs各個子類在各個工具類中的使用進行驗證。
對每個考察會從如下幾個方面來進行
工具類的主要作用
主要擷取鎖方法(其他的類似方法如對應的可以更好的進行中斷和逾時或者異步等特性)
主要釋放鎖方法(其他的類似方法如對應的可以更好的進行中斷和逾時或者異步等特性)
工具類的構造方法(構造方法能告訴我們一個類最在意,最根本的屬性)
sync構造方法
sync接口方法
sync對aqs方法的override
state的作用
state維護重要邏輯
我們的問題就是這些aqs的子類如何配合父類aqs的架構方法來完成各個工具類不同的鎖需求。分析思路是這樣:
這個工具類是幹什麼用的?可以了解為是功能需求。
這個工具類是通過哪些方法來實作這些功能的?可以了解為分解的需求
aqs的子類sync是如何支援這些方法的?可以了解為需求的實作。
按照如下的思路對每個工具類嘗試進行解析,隻是注意以上觀點,可能并沒有覆寫這個工具類的所有内容(其實就是方法)和對應sync的所有内容。為了表達清楚些,把重點方法的代碼引用在文章中,并對重點語句做了标記。因為五鐘同步工具類在一起說明,看上去引用的代碼有點多。
先看doc中對semaphore的功能要求:
a counting semaphore. conceptually, a semaphore maintains a set of permits. each <code>acquire</code> blocks if necessary until a permit is available, and then takes it. each <code>release</code> adds a permit, potentially releasing a blocking acquirer. however, no actual permit objects are used; the <code>semaphore</code> just keeps a count of the number available and acts accordingly.
信号量semaphore的主要作用是來控制同時通路某個特定資源的操作數量,或者同時執行某個指定操作的數量。 semaphore隻是計數,不包括許可對象,并且semaphore也不會把許可與線程對象關聯起來,是以一個線程中獲得的許可可以在另外一個線程中釋放。關于這點的了解可以參照what is mutex and semaphore in java ? what is the main difference ?的說明。 semphore對外的兩個方法是 acquire()和release()方法。在許可可用前會阻塞每一個 acquire(),然後再擷取該許可。每調用 release() 添加一個許可,釋放一個正在阻塞的擷取者。
達到這樣的操作是通過同步器sync來操作,可以是fairsync,也可以是nonfairsync。 從sync的構造方法中,就可以看出semphore中所謂的permit其實就是aqs中的state。
工具類是通過sync的acquiresharedinterruptibly和releaseshared的方法提供功能。aqs中定義的這兩個final方法調用的是子類對應的try*方法。在這裡覆寫了tryacquireshared和tryreleaseshared方法。每一次請求acquire()一個許可都會導緻計數器減少1,同樣每次釋放一個許可release()都會導緻計數器增加1,一旦達到了0,新的許可請求線程将被挂起。
每次釋放鎖時先調用該方法時,作用就修改state值為state+release,即表示增加新釋放的許可數。 而tryacquireshared對應于fairsync,nonfairsync有兩種不同的實作。 fairsync中,總是判斷目前線程是等待隊列的第一個線程時,獲得鎖,且修改state值為state-acquires。
對nonfairsync,不用考慮等待隊列,直接修改state許可數。
即不管是公平還是非公平,acquire方法總是會判斷是否還有許可可用,如果有,并且目前線程可以獲得,則獲得鎖,許可數相應減少。state在此的作用就是許可數。
總結:在semaphore中使用aqs的子類sync,初始化state表示許可數,在每一次請求acquire()一個許可都會導緻計數器減少1,同樣每次釋放一個許可release()都會導緻計數器增加1。一旦達到了0,新的許可請求線程将被挂起。
要求完成的功能是: a synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes. a <code>countdownlatch</code> is initialized with a given count. the <code>await</code> methods block until the current count reaches zero due to invocations of the <code>countdown</code> method, after which all waiting threads are released and any subsequent invocations of <code>await</code> return immediately.
就像名字latch所表達的一樣,把一組線程全部關在外面,在某個狀态時候放開。即一種同步機制來保證一個或多個線程等待其他線程完成。初始化了一個count計數,當count未遞減到0時候,每次調用await方法都會阻塞。每次調用countdown來是的的count遞減。 這是<code>countdownlatch</code> 中“規定”的該工具類應該滿足的功能,詳細的使用的例子不再此介紹。隻是分析如何借助sync同步器來達到以上功能的。 從構造函數中可以看到該類也維護了一個計數count。這個計數其實也是通過aqs的state來完成的,
countdownlatch的兩個重要方法是await和countdown方法。定義分别如下。定義await方法的作用是在計數器不為0時候阻塞調用線程,為0時候立即傳回;countdown方法的作用是計數遞減。
看到這兩個方法最終的執行還是同步器中的對應方法。在countdownlatch中也定義了一個繼承于aqs的sync。在前面的分析中知道父類的acquiresharedinterruptibly方法和releaseshared其實是分别調用到了子類中定義的tryacquireshared和tryreleaseshared方法。 在countdownlatch的sync類中也就僅僅實作了這兩個方法。
其中tryacquireshared方法内容非常簡單,隻是一個三元表達式,但是這個state值為0指派1,不為0卻指派-1。看着不太符合我們一般的用法,這主要是為了配合父類aqs中的邏輯。當state為0表示計數遞減完成,則傳回值為-1,在父類中滿足條件則執行後續的阻塞操作;當state不為0表示電腦遞減未完成,則傳回值為1,在父類調用中直接方法結束,不阻塞。
tryreleaseshared方法主要是對state值的維護,當已經為0,則傳回false,父類releaseshared方法直接傳回;當state不為0(其實就是大于0,因為count初始化是一個正數),則遞減,并通過cas的方式更新state的值。
總結:<code>countdownlatch</code> 委托自定義的sync中的,await()和countdown()方法來完成阻塞線程到計數器為0的功能和計數器遞減功能。而該這兩個方法委托給自定義的sync的acquiresharedinterruptibly()和releaseshared(int arg)方法。真正實作對state(count)維護的是父類aqs中調用子類定義的<code>tryacquireshared(int)和</code><code>tryreleaseshared(int)來維護計數count。計數count使用的是aqs的狀态位state。每次調用countdown方法計數遞減,在計數遞減到0之前,調用await的線程都會阻塞。</code>
名字翻譯很好,可重入鎖。功能需求如下 a reentrant mutual exclusion <code>lock</code> with the same basic behavior and semantics as the implicit monitor lock accessed using <code>synchronized</code> methods and statements, but with extended capabilities. a <code>reentrantlock</code> is owned by the thread last successfully locking, but not yet unlocking it. a thread invoking <code>lock</code> will return, successfully acquiring the lock, when the lock is not owned by another thread. the method will return immediately if the current thread already owns the lock. this can be checked using methods <code>isheldbycurrentthread</code>, and <code>getholdcount</code>.
可重入鎖應該是幾種同步工具裡面被用的對多的一個。标準的互斥操作,也就是一次隻能有一個線程持有鎖,可能是aqs中最重要的一個類。基本功能就關鍵字synchronize所支援的功能。關于reentrantlock和synchronize的差别比較等文章很多,可以參照java 理論與實踐: jdk 5.0 中更靈活、更具可伸縮性的鎖定機制和《java concurrency in practice》的對應章節。 reentrantlock對外的主要方法是lock(),trylock()和unlock()方法,當然還有其他變種的lockinterruptibly()、trylock(long timeout, timeunit unit)等。
lock的功能是擷取鎖。如果沒有線程使用則立即傳回,并設定state為1;如果目前線程已經占有鎖,則state加1;如果其他線程占有鎖,則目前線程不可用,等待。
trylock的功能是 如果鎖可用,則擷取鎖,并立即傳回值 true。如果鎖不可用,立即傳回值 false。
unlock的功能是嘗試釋放鎖,如果目前線程占有鎖則count減一,如果count為0則釋放鎖。若占有線程不是目前線程,則抛異常。
可以看到也是借助sync來完成,我們下面詳細看下sync是如何實作這些”規定”的需求的。reentrantlock的構造函數告訴我們,其支援公平和非公平兩種鎖機制。
在該類中對應定了兩種fairsync和nonfairsync兩種同步器,都繼承者aqs。可以看到對應執行的是lock、release、和sync的nonfairtryacquire。從前面aqs源碼知道release是在父類aqs中定義的方法,lock和nonfairtryacquire是這個sync中特定的方法,不是對父類對應方法的覆寫。 lock方法有對于fairsync和nofairsync有兩種不同的實作,對于非公平鎖隻要目前沒有線程持有鎖,就将鎖給目前線程;而公平鎖不能這麼做,總是調用acquire方法來和其他線程一樣公平的嘗試擷取鎖。
acquire(int arg)方法是在父類aqs中定義,在其實作中先會調用子類的tryacquire(int arg)方法。 對于非公平鎖,通過state是否為0判斷,目前是否有線程持有鎖,如果沒有則把鎖配置設定給目前線程;否則如果state不為0,說明目前有線程持有鎖,則判斷持有鎖的線程是否就是目前線程,如果是增加state計數,表示持有鎖的線程的重入次數增加。當然增加重入數也會檢查是否超過最大值。
對于公平鎖,其tryacquire(int arg)方法中,如果state為0表示沒有線程持有鎖,會檢查目前線程是否是等待隊列的第一個線程,如果是則配置設定鎖給目前線程;否則如果state不為0,說明目前有線程持有鎖,則判斷持有鎖的線程釋放就是目前線程,如果是增加state計數,表示持有鎖的線程的重入次數增加。
比較公平鎖機制和非公平鎖機制的差别僅僅在于如果目前沒有線程持有鎖,是優先把鎖配置設定給目前線程,還是優先配置設定給等待隊列中隊首的線程。 釋放鎖時候調用aqs的release(int arg)方法,前面定義知道父類的該方法會先調用子類的tryrelease(int arg)方法。在該方法中主要作用是state狀态位減少release個,表示釋放鎖,如果更新後的state為0;表示目前線程釋放鎖,如果不為0,表示持有鎖的目前線程重入數減少。
總結: reentrantlock中定義的同步器分為公平的同步器和非公平的同步器。在該同步器中state狀态位表示目前持有鎖的線程的重入次數。在擷取鎖時,通過覆寫aqs的tryacquire(int arg)方法,如果沒有線程持有則立即傳回,并設定state為1;如果目前線程已經占有鎖,則state加1;如果其他線程占有鎖,則目前線程不可用。釋放鎖時,覆寫了aqs的tryrelease(int arg),在該方法中主要作用是state狀态位減少release個,表示釋放鎖,如果更新後的state為0,表示目前線程釋放鎖,如果不為0,表示持有鎖的目前線程重入數減少。
讀寫鎖的要求是: a readwritelock maintains a pair of associated <code>locks</code>, one for read-only operations and one for writing. the <code>read lock</code> may be held simultaneously by multiple reader threads, so long as there are no writers. the <code>write lock</code> is exclusive. all readwritelock implementations must guarantee that the memory synchronization effects of writelock operations (as specified in the <code>lock</code> interface) also hold with respect to the associated readlock. that is, a thread successfully acquiring the read lock will see all updates made upon previous release of the write lock.
即讀和讀之間是相容的,寫和任何操作都是排他的。這種鎖機制在資料庫系統理論中應用的其實更為普遍。 允許多個讀線程同時持有鎖,但是隻有一個寫線程可以持有鎖。讀寫鎖允許讀線程和寫線程按照請求鎖的順序重新擷取讀取鎖或者寫入鎖。當然了隻有寫線程釋放了鎖,讀線程才能擷取重入鎖。寫線程擷取寫入鎖後可以再次擷取讀取鎖,但是讀線程擷取讀取鎖後卻不能擷取寫入鎖。 reentrantreadwritelock鎖從其要求的功能上來看,是對前面的reentrantlock的擴充,是以功能複雜度上來說也提高了,看看該類下面定義的内部類,除了支援公平非公平的sync外,還有兩種不同的鎖,readlock和writelock。
在向下進行之前,有必要回答這樣一個問題,writelock和readlock好像完成的功能不一樣,看上去似乎是兩把鎖。reentrantreadwritelock中分别通過兩個public的方法readlock()和writelock()獲得讀鎖和寫鎖。
但是如果是兩把鎖,可以實作前面功能要求的讀鎖和讀鎖直接的相容,寫鎖和寫鎖直接的互斥,這本身共享鎖和排他鎖就能滿足要求,但是如何實作對同一個對象上讀和寫的控制?明顯,隻有一把鎖才能做到。 看上面代碼片段時候,不小心看到了一個熟悉的字段sync,前面的幾個同步工具我們知道了,這些工具類的所有操作最終都是委托給aqs的對應子類sync來完成,這裡隻有一個同步器sync,那是不是就是隻有一把鎖呢。看看後面的構造函數會驗證我們的猜想。
沒錯,readlock和writelock使用的其實是一個private的同步器sync。 下面看下可重入讀寫鎖提供哪些鎖的方法來滿足上面的需求的。
看到readlock提供了lock()、lockinterruptibly()、trylock()、trylock(long timeout, timeunit unit)和unlock()方法。我們看下主要的幾個方法的實作如下:lock()方法的作用是擷取讀鎖;trylock()的作用是嘗試目前沒有其他線程目前持有寫鎖時擷取讀鎖;unlock方法的作用是釋放讀鎖。
分别調用到sync的三個方法acquireshared(int arg) 、releaseshared(int arg)和 tryreadlock()方法,其中前兩個是aqs父類中定義的,後一個是該sync中根據自己需要實作的方法。 前面aqs父類的介紹中知道,acquireshared(int arg) 和releaseshared(int arg)方法是在父類中定義的,調用子類的對應try字型的方法,我們看下在子類sync中定義對應的try*字型的方法怎麼滿足功能的。先看acquireshared中定義的tryacquireshared
嘗試擷取讀鎖的方法是這樣的:如果有排他鎖,但是持有排他鎖的線程不是目前線程,則擷取失敗;否則如果已經加讀鎖的個數超過允許的最大值,抛出異常;否則檢查是否需要阻塞目前線程,如果不阻塞,則使用cas的方式給更新狀态位state。其中readershouldblock在sync的兩個子類中實作,根據公平非公平的政策有不同的判斷條件。
對應的releaseshared中調用的tryreleaseshared定義如下
可以看到主要的作用在準備釋放讀鎖時更新狀态位的值。 sync中提供給readlock用的tryreadlock方法和tryacquireshared内容和邏輯差不多,而且本文想着重分析的sync對父類aqs的方法如何改變來達到需要的功能,是以這個方法這裡不分析了。 可以看到加鎖時候state增加了一個shared_unit,在釋放鎖時state減少了一個shared_unit。為什麼是shared_unit,而不是1呢?這個看了下面兩個方法的定義就比較清楚了。
原來為了隻用一個state狀态位來表示兩種鎖的資訊,高位16位表示共享鎖的狀态位,低位16位表示獨占鎖的狀态位。至于讀鎖和寫鎖的狀态位的意思,随着後面分析會逐漸更清楚。 在看到這裡的時候,讀鎖的狀态位的意思應該是比較清楚,表示目前持有共享鎖的線程數。有一個新的線程過了想使用共享鎖,如果其他線程也隻是加了共享鎖,則目前線程就可以加共享鎖,每加一次,狀态位遞加一些,因為存儲在高16位,是以遞加時是加一個shared_unit。
接着關注下writelock的方法。和 readlock 類似,提供出來的還是lock()、trylock()、unlock()三個和其相似方法。
看到分别調用了sync的acquire() release() 和trywritelock方法,其中前兩個都是定義在父類aqs的方法。調用了子類定義的對應try字型的方法。tryacquire和tryrelease方法。這裡我們就看下子類的這兩個try*字型的方法做了哪些事情。
tryacquire中嘗試擷取排他鎖。結合排他鎖的語義和代碼邏輯不難看到:通過state的判斷,當有讀鎖時擷取不成功,當有寫鎖,如果持有寫鎖的線程不是目前線程,則擷取不成功。如果可以擷取,則cas的方式更新state,并設定目前線程排他的擷取鎖。writershouldblock定義在sync的子類中,對于fairesync和unfairsync有不同的判斷。 接下來看tryrelease方法,主要作用是在釋放排他鎖時候更新state,減去releases的數目。看到這裡發現寫鎖中用到的sync和可重入鎖reentrantlock整個邏輯都對應的差不多。
隻是觀察到寫鎖state更新加和減和前面的幾種比較類似,直接操作的就是傳入的整形參數,這在讀鎖的時候講過了,因為排他鎖的狀态位是存儲在state的低16位。
總結: reentrantreadwritelock中提供了兩個lock:reentrantreadwritelock.readlock和reentrantreadwritelock.writelock。對外提供功能的是兩個lock,但是内部封裝的是一個同步器sync,有公平和不公平兩個版本。借用了aqs的state狀态位來儲存鎖的計數資訊。高16位表示共享鎖的數量,低16位表示獨占鎖的重入次數。在aqs子類的對應try字型方法中實作對state的維護。
先看需求 a cancellable asynchronous computation. this class provides a base implementation of future, with methods to start and cancel a computation, query to see if the computation is complete, and retrieve the result of the computation. the result can only be retrieved when the computation has completed; the get method will block if the computation has not yet completed. once the computation has completed, the computation cannot be restarted or cancelled.
了解其核心需求是,一個執行任務,開始執行後可以被取消,可以檢視執行結果,如果執行結果未完成則阻塞。 一般表示一個輸入待執行任務。線上程池中futuretask中一般的用法就是構造一個futuretask,然後送出execute,傳回的類型還是futuretask,調用其get方法即可得到執行結果。 run方法定義的就是任務執行的内容,在工作線程中被調用。通過構造函數可以看到futuretask封裝的了一個runnable的對象,另外一個泛型參數result。猜也可以猜到前者就是執行的任務内容,後者是來接收執行結果的。可以看到功能還是委托給sync對象,構造的參數是一個有執行結果的調用callable,也可以直接使用一個callable參數。
futuretask實作了runnablefuture接口,也即實作了runnable和future接口。作業線程執行的内容是futuretask的的run方法内定義的任務内容。如線程池 threadpoolexecutor.worker.runtask(runnable task)方法可以看到線上程池的worker線程中調用到執行任務的run方法。這裡使用sync的作用,就是在任務執行線程和送出任務(同時也是擷取任務執行結果)的線程之間維持一個鎖的關系,保證隻有執行結束後才能擷取到結果。
futuretask的任務執行方法是
擷取執行結果的方法是
設定執行結果的方法是
能看到,都是調到對應的sync的對應方法。最主要的是innerrun方法,通過cas的方式設定任務執行狀态位running,執行傳入的回調,并把執行結果調用innerset進行指派。
在innerset方法中設定執行狀态位為執行結束,并把執行結果指派給result。
前面方法把執行結果放在result中,我們知道future接口定義的get方法來擷取執行結果,那如何來判斷另外一個線程已經執行完畢呢?看到futuretask的get方法還是調用到sync的innerget方法。 innerget方法根據判斷執行狀态來擷取執行結果。acquiresharedinterruptibly方法其實調用的是子類中定義的tryacquireshared來判斷任務釋放執行完畢或者取消。如果未完畢或取消,則挂起目前線程。
tryacquireshared方法的定義如下,調用innerisdone方法,根據state的狀态值做出判斷,如果結束則傳回1,未結束傳回-1。當tryacquireshared傳回-1,則在父類aqs中擷取共享鎖的線程會阻塞。即實作“任務未完成調用get方法的線程會阻塞”這樣的功能。
tryreleaseshared沒有做什麼事情,因為不像前面四種其實都有鎖的意味,需要釋放鎖。在futuretask中state表示任務的執行狀态,在幾乎每個方法的開始都會判讀和設定狀态。
總結:在futuretask實作了異步的執行和送出,作為可以被executor送出的對象。通過sync來維護任務的執行狀态,進而保證隻有工作線程任務執行完後,其他線程才能擷取到執行結果。aqs的子類sync在這裡主要是借用state狀态位來存儲執行狀态,來完成對對各種狀态以及加鎖、阻塞的實作。
最後終于了解了這早前就算了解的類,名字為什麼叫futuretask,實作了future接口(滿足在future的某個時間擷取執行結果,這是future接口的取名的意義吧),另外在執行中作為對執行任務的封裝,封裝了執行的任務内容,同時也封裝了執行結果,可以安全的把這個任務交給另外的線程去執行,隻要執行get方法能得到結果,則一定是你想要的結果,真的是很精妙。
本文主要側重aqs的子類在各個同步工具類中的使用情況,其實也基本涵蓋了這幾個同步工具類的主要邏輯,但目标并不是對這幾個同步工具類的代碼進行詳細解析。另外aqs本身的幾個final方法,才是同步器的公共基礎,也不是本文的主題,也未詳細展開。其實寫這篇文章的一個初始目的真的隻是想列出如下表格,對比下aqs中的各個子類是怎麼使用state的,居然啰嗦了這麼多。
工具類
工具類作用
工具類加鎖方法
工具類釋放鎖方法
sync覆寫的方法
sync非覆寫的重要方法
鎖類型
鎖維護
semaphore
控制同時通路某個特定資源的操作數量
acquire:每次請求一個許可都會導緻計數器減少1,,一旦達到了0,新的許可請求線程将被挂起
release:每調用 添加一個許可,釋放一個正在阻塞的擷取者
tryacquireshared tryreleaseshared
表示初始化的許可數
共享鎖
每一次請求acquire()一個許可都會導緻計數器減少1,同樣每次釋放一個許可release()都會導緻計數器增加1,一旦達到了0,新的許可請求線程将被挂起。
countdownlatch
把一組線程全部關在外面,在某個狀态時候放開。一種同步機制來保證一個或多個線程等待其他線程完成。
await:在計數器不為0時候阻塞調用線程,為0時候立即傳回
countdown :計數遞減
維護一個計數器
初始化一個計數,每次調用countdown方法計數遞減,在計數遞減到0之前,調用await的線程都會阻塞
reentrantlock
标準的互斥操作,也就是一次隻能有一個線程持有鎖
lock:如果沒有線程使用則立即傳回,并設定state為1;如果目前線程已經占有鎖,則state加1;如果其他線程占有鎖,則目前線程不可用,等待 trylock:如果鎖可用,則擷取鎖,并立即傳回值 true。如果鎖不可用,則此方法将立即傳回值 false
unlock:嘗試釋放鎖,如果目前線程占有鎖則count減一,如果count為0則釋放鎖。如果占有線程不是目前線程,則抛異常
tryacquire tryrelease
nonfairtryacquir
state表示獲得鎖的線程對鎖的重入次數。
排他鎖。
擷取鎖時,如果沒有線程使用則立即傳回,并設定state為1;如果目前線程已經占有鎖,則state加1;如果其他線程占有鎖,則目前線程不可用。釋放鎖時,在該方法中主要作用是state狀态位減少release個,表示釋放鎖,如果更新後的state為0,表示目前線程釋放鎖,如果不為0,表示持有鎖的目前線程重入數減少
reentrantreadwritelock
讀寫鎖。允許多個讀線程同時持有鎖,但是隻有一個寫線程可以持有鎖。寫線程擷取寫入鎖後可以再次擷取讀取鎖,但是讀線程擷取讀取鎖後卻不能擷取寫入鎖
readlock#lock :擷取讀鎖 readlock#trylock:嘗試目前沒有其他線程目前持有寫鎖時擷取讀鎖 writelock#lock:擷取寫鎖 writelock#trylock:嘗試目前沒有其他線程持有寫鎖時,呼氣寫鎖。
readlock#unlock:釋放讀鎖 writelock#unlock:釋放寫鎖
acquireshared releaseshared tryacquire tryrelease
tryreadlock trywritelock
高16位表示共享鎖的數量,低16位表示獨占鎖的重入次數
讀鎖:共享 寫鎖:排他
對于共享鎖,state是計數器的概念。一個共享鎖就相對于一次計數器操作,一次擷取共享鎖相當于計數器加1,釋放一個共享鎖就相當于計數器減1;排他鎖維護類似于可重入鎖。
futuretask
封裝一個執行任務交給其他線程去執行,開始執行後可以被取消,可以檢視執行結果,如果執行結果未完成則阻塞。
v get()
run() set(v) cancel(boolean)
innerget innerrun() innerset inneriscancelled
state狀态位來存儲執行狀态running、run、cancelled
擷取執行結果的線程(可以有多個)一直阻塞,直到執行任務的線程執行完畢,或者執行任務被取消。
完。