談到併發,不得不談ReentrantLock;而談到ReentrantLock,不得不談AbstractQueuedSynchronizer(AQS)!html
類如其名,抽象的隊列式的同步器,AQS定義了一套多線程訪問共享資源的同步器框架,許多同步類實現都依賴於它,如經常使用的ReentrantLock/Semaphore/CountDownLatch...java
併發包的底層就是使用AQS實現的,如下是AQS的類圖結構node
它維護了一個volatile int state(表明共享資源)和一個FIFO線程等待隊列(多線程競爭資源被阻塞會進入此隊列)。這裏volatile保證線程可見性。編程
state的訪問方式有三種:安全
getState()多線程
setState()併發
compareAndSetState()框架
這三種都是原子操做,其中compareAndSetState的實現依賴於Unsafe的compareAndSwapInt()方法。代碼以下:函數
protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
AQS定義了兩種資源共享方式:Exclusive(獨佔,只有一個線程能執行,如ReentantLock)和Share(共享,多個線程可同時執行,如Semaphore/CountDownLatch)。oop
不一樣的自定義同步器爭用共享資源的方式也不一樣,自定義同步器在實現時只須要實現共享資源state的獲取與釋放方式便可,至於具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了。自定義同步器實現時主要實現如下幾種方法。
isHeldExclusively():該線程是否正在獨佔資源。只有用到condition才須要去實現它。
tryAcquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。
tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。
tryAcquireShared(int):共享方式。嘗試獲取資源,負數表示失敗;0表示成功,但沒用剩餘可用資源;正數表示成功,且有剩餘資源。
tryReleaseShared(int):共享方式。嘗試釋放資源,若是釋放後容許喚醒後續等待節點返回true,不然返回false。
以ReentrantLock爲例,state初始化爲0,表示未鎖定狀態。A線程lock()時,會調用tryAcquire()獨佔該鎖並將state+1。此後,其餘線程再tryAcquire()時就會失敗,直到A線程unlock()到state=0(即釋放鎖)爲止,其它線程纔有機會獲取該鎖。固然,釋放鎖以前,A線程本身是能夠重複獲取此鎖的(state會累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多麼次,這樣才能保證state是能回到零態的。
再以CountDownLatch以例,任務分爲N個子線程去執行,state也初始化爲N(注意N要與線程個數一致)。這N個子線程是並行執行的,每一個子線程執行完後countDown()一次,state會CAS減1。等到全部子線程都執行完後(即state=0),會unpark()主調用線程,而後主調用線程就會從await()函數返回,繼續後餘動做。
通常來講,自定義同步器要麼是獨佔方法,要麼是共享方式,他們也只需實現tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種便可。但AQS也支持自定義同步器同時實現獨佔和共享兩種方式,如ReentrantReadWriteLock。
接下來咱們開始開始講解AQS的源碼實現。依照acquire-release、acquireShared-releaseShared的次序來。
acquire是一種以獨佔方式獲取資源,若是獲取到資源,線程直接返回,不然進入等待隊列,直到獲取到資源爲止,且整個過程忽略中斷的影響。該方法是獨佔模式下線程獲取共享資源的頂層入口。
獲取到資源後,線程就能夠去執行其臨界區代碼了。下面是acquire()的源碼
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
經過註釋咱們知道,acquire方法是一種互斥模式,且忽略中斷。該方法至少執行一次tryAcquire(int)
方法,若是tryAcquire(int)
方法返回true,則acquire直接返回,不然當前線程須要進入隊列進行排隊。函數流程以下
一、tryAcquire():嘗試直接獲取資源,若是成功則直接返回;
二、addWaiter():將該線程加入等待隊列的尾部,並標記爲獨佔模式;
三、acquireQueued():使線程在等待隊列中獲取資源,一直獲取到資源後才返回。若是在整個等待過程當中被中斷過,則返回true,不然返回false。
四、若是線程在等待過程當中被中斷過,它是不響應的。只有獲取資源後纔再進行自我中斷selfInterrupt(),將中斷補上。
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
tryAcquire嘗試以獨佔的方式獲取資源,若是獲取成功,則直接返回true,不然直接返回false。該方法能夠用於實現Lock中的tryLock()方法。該方法的默認實現是拋出UnsupportedOperationException異常,
什麼?直接throw異常?說好的功能呢?好吧,還記得概述裏講的AQS只是一個框架,具體資源的獲取/釋放方式交由自定義同步器去實現嗎?就是這裏了!!!AQS這裏只定義了一個接口,具體資源的獲取交由
自定義同步器去實現了(經過state的get/set/CAS)!!!至於能不能重入,能不能加塞,那就看具體的自定義同步器怎麼去設計了!!!固然,自定義同步器在進行資源訪問時要考慮線程安全的影響。
這裏之因此沒有定義成abstract,是由於獨佔模式下只用實現tryAcquire-tryRelease,而共享模式下只用實現tryAcquireShared-tryReleaseShared。若是都定義成abstract,那麼每一個模式也要去實現另外一模式下的接口。
說到底,Doug Lea仍是站在我們開發者的角度,儘可能減小沒必要要的工做量。
/** * Creates and enqueues node for current thread and given mode. * * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared * @return the new node */ private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); //——以給定模式構造節點。mode有兩種:EXCLUSIVE(獨佔)和SHARED(共享) // Try the fast path of enq; backup to full enq on failure Node pred = tail; //——嘗試快速方式直接放到隊尾 if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); //——上一步失敗則經過enq入隊 return node; }
不用再說了,直接看註釋吧。這裏咱們說下Node。Node結點是對每個訪問同步代碼的線程的封裝,其包含了須要同步的線程自己以及線程的狀態,如是否被阻塞,是否等待喚醒,是否已經被取消等。變量waitStatus則表示當前被封裝成Node結點的等待狀態,共有4種取值CANCELLED、SIGNAL、CONDITION、PROPAGATE。
CANCELLED:值爲1,在同步隊列中等待的線程等待超時或被中斷,須要從同步隊列中取消該Node的結點,其結點的waitStatus爲CANCELLED,即結束狀態,進入該狀態後的結點將不會再變化。
SIGNAL:值爲-1,被標識爲該等待喚醒狀態的後繼結點,當其前繼結點的線程釋放了同步鎖或被取消,將會通知該後繼結點的線程執行。說白了,就是處於喚醒狀態,只要前繼結點釋放鎖,就會通知標識爲SIGNAL狀態的後繼結點的線程執行。
CONDITION:值爲-2,與Condition相關,該標識的結點處於等待隊列中,結點的線程等待在Condition上,當其餘線程調用了Condition的signal()方法後,CONDITION狀態的結點將從等待隊列轉移到同步隊列中,等待獲取同步鎖。
PROPAGATE:值爲-3,與共享模式相關,在共享模式中,該狀態標識結點的線程處於可運行狀態。
0狀態:值爲0,表明初始化狀態。
AQS在判斷狀態時,經過用waitStatus>0表示取消狀態,而waitStatus<0表示有效狀態。
/** * Inserts node into queue, initializing if necessary. See picture above. * @param node the node to insert * @return node's predecessor */ private Node enq(final Node node) { for (;;) { //——CAS自旋,直到成功加入隊尾 Node t = tail; if (t == null) { // Must initialize //——隊列爲空,建立一個空的標示節點做爲head節點,並將tail也指向它 if (compareAndSetHead(new Node())) tail = head; } else { //——正常流程放入隊尾 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
enq(node)用於將當前節點插入到等待隊列,若是隊列爲空,則初始化當前隊列。整個過程以CAS自旋的方式進行,直到成功加入隊尾爲止。
OK,經過tryAcquire()和addWaiter(),該線程獲取資源失敗,已經被放入等待隊列尾部了。聰明的你馬上應該能想到該線程下一部該幹什麼了吧:進入等待狀態休息,直到其餘線程完全釋放資源後喚醒本身,本身再拿到資源,而後就能夠去幹本身想幹的事了。沒錯,就是這樣!是否是跟醫院排隊拿號有點類似~~acquireQueued()就是幹這件事:在等待隊列中排隊拿號(中間沒其它事幹能夠休息),直到拿到號後再返回。這個函數很是關鍵,仍是上源碼吧:
final boolean acquireQueued(final Node node, int arg) { boolean failed = true;//——標記是否成功拿到資源,默認是false try { boolean interrupted = false;//——標記等待過程是否被中斷過 for (;;) { //——又是一個自旋! final Node p = node.predecessor(); //——拿到前驅 if (p == head && tryAcquire(arg)) { //——若是前驅是head,即該節點已成爲老二,那麼便有資源去嘗試獲取資源(多是老大釋放完資源後喚醒本身的,固然也可能被interrupt了) setHead(node); //——拿到資源後,將head指向該節點。因此head所指向的標杆節點,就是當前獲取到資源的那個節點或null p.next = null; // help GC //setHead中的node.prev以置爲null,此處再將head.next置爲null,就是爲了方便GC回收之前的head節點。也就意味着以前拿完資源的節點出隊了! failed = false; return interrupted; //——返回等待過程當中是否被中斷過 } //若是本身能夠休息了,就進入waiting狀態,直到被unpark() if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; //若是等待過程被中斷過了,哪怕只有那麼一次,就將interrupted標記爲true } } finally { if (failed) cancelAcquire(node); } }
到這裏了,咱們先不急着總結acquireQueued()的函數流程,先看看shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()具體幹些什麼。
此方法主要用於檢查狀態,看看本身是否真的能夠去休息了,進入waiting狀態
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; //——拿到前驅狀態 if (ws == Node.SIGNAL) //——若是已經告訴前驅拿完號後通知本身一下,那就能夠安心休息了 /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { //——若是前驅放棄了,那就一直往前找,直到找到一個最近正常等待的狀態,並排在它的後邊,注意哪些放棄的節點,因爲被本身加塞到他們前邊,他們至關於造成了一個無引用鏈,稍後GC回收。 /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); //——若是前驅正常,那就把前驅的狀態設置爲SIGNAL,告訴它拿完號後通知本身一下,有可能失敗,人家說不定剛釋放完呢! } return false; }
整個流程中,若是前驅結點的狀態不是SIGNAL,那麼本身就不能安心去休息,須要去找個安心的休息點,同時能夠再嘗試下看有沒有機會輪到本身拿號。
若是線程找好安全休息點後,那就能夠安心去休息了。此方法就是讓線程去休息,真正進入等待狀態。
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
park()會讓當前線程進入waiting狀態。在此狀態下,有兩種途徑能夠喚醒該線程:1)被unpark();2)被interrupt()。
OK,看了shouldParkAfterFailedAcquire()和parkAndCheckInterrupt(),如今讓咱們再回到acquireQueued(),總結下該函數的具體流程:
一、節點進入隊尾後,檢查狀態,找到安全休息點
二、調用park()進入waiting狀態,等待unpark()或interrupt()喚醒本身
三、被喚醒後,看本身是否是有資格能拿到號。若是能拿到,head指向當前節點,並返回從入隊到拿到號的整個過程當中是否被中斷過;若是沒用拿到,繼續流程1
OKOK,acquireQueued()分析完以後,咱們接下來再回到acquire()!再貼上它的源碼吧:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
再來總結下它的流程吧:
一、調用自定義同步器的tryAcquire()嘗試直接去獲取資源,若是成功則直接返回;
二、沒成功,則執行addWaiter()將線程加入等待隊列的尾部並標記爲獨佔模式;
三、acquireQueued()使線程在等待隊列中休息,有機會時(輪到本身,會被unpark())會去嘗試獲取資源。獲取到資源才返回。若是在整個等待過程當中被中斷過,則會返回true,不然返回false。
四、若是線程在等待過程當中被中斷過,他是不響應的。只是獲取資源後才進行自我中斷selfInterrupt(),將中斷補上。
至此,acquire()的流程終於算是告一段落了。這也就是ReentrantLock.lock()的流程,不信你去看其lock()源碼吧,整個函數就是一條acquire(1)!!!
上一小節已經把acquire()說完了,這一小節就來說講它的反操做release()吧。此方法是獨佔模式下線程釋放共享資源的頂層入口。它會釋放指定量的資源,
若是完全釋放了(即state=0),它會喚醒等待隊列裏的其餘線程來獲取資源。這也正是unlock()的語義,固然不只僅只限於unlock()。下面是release()的源碼:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; //——找到頭節點 if (h != null && h.waitStatus != 0) unparkSuccessor(h); //——喚醒等待隊列裏的下一個線程 return true; } return false; }
邏輯並不複雜。它調用tryRelease()來釋放資源。有一點須要注意的是,它是根據tryRelease()的返回值來判斷該線程是否已經完成釋放掉資源了!因此自定義同步器在設計tryRelease()的時候要明確這一點!!
此方法嘗試去釋放指定量的資源。下面是tryRelease()的源碼:
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); }
跟tryAcquire()同樣,這個方法是須要獨佔模式的自定義同步器去實現的。正常來講,tryRelease()都會成功的,由於這是獨佔模式,該線程來釋放資源,那麼它確定已經拿到獨佔資源了,直接減掉相應量的資源便可(state-=arg),也不須要考慮線程安全的問題。但要注意它的返回值,上面已經提到了,release()是根據tryRelease()的返回值來判斷該線程是否已經完成釋放掉資源了!因此自義定同步器在實現時,若是已經完全釋放資源(state=0),要返回true,不然返回false。
此方法用於喚醒等待隊列中下一個線程。下面是源碼:
private void unparkSuccessor(Node node) { int ws = node.waitStatus; //——這裏node通常爲當前線程所在的節點 if (ws < 0) //——置0當前線程所在的節點狀態,容許失敗 compareAndSetWaitStatus(node, ws, 0); Node s = node.next; //——找到下一個須要喚醒的結點 if (s == null || s.waitStatus > 0) { //——若是爲空,或已取消 s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) //——這裏看到<=0的結點,仍是有效的結點 s = t; } if (s != null) LockSupport.unpark(s.thread); //——喚醒 }
這個函數並不複雜。一句話歸納:用unpark()喚醒等待隊列中最前邊的那個未放棄線程,這裏咱們也用s來表示吧。此時,再和acquireQueued()聯繫起來,s被喚醒後,進入if (p == head && tryAcquire(arg))的判斷(即便p!=head也不要緊,它會再進入shouldParkAfterFailedAcquire()尋找一個安全點。這裏既然s已是等待隊列中最前邊的那個未放棄線程了,那麼經過shouldParkAfterFailedAcquire()的調整,s也必然會跑到head的next結點,下一次自旋p==head就成立啦),而後s把本身設置成head標杆結點,表示本身已經獲取到資源了,acquire()也返回了!!And then, DO what you WANT!
release()是獨佔模式下線程釋放共享資源的頂層入口。它會釋放指定量的資源,若是完全釋放了(即state=0),它會喚醒等待隊列裏的其餘線程來獲取資源。
此方法是共享模式下線程獲取共享資源的頂層入口。它會獲取指定量的資源,獲取成功則直接返回,獲取失敗則進入等待隊列,直到獲取到資源爲止,整個過程忽略中斷。下面是acquireShared()的源碼:
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
這裏tryAcquireShared()依然須要自定義同步器去實現。可是AQS已經把其返回值的語義定義好了:負值表明獲取失敗;0表明獲取成功,但沒有剩餘資源;正數表示獲取成功,還有剩餘資源,其餘線程還能夠去獲取。因此這裏acquireShared()的流程就是: tryAcquireShared()嘗試獲取資源,成功則直接返回; 失敗則經過doAcquireShared()進入等待隊列,直到獲取到資源爲止才返回。
此方法用於將當前線程加入等待隊列尾部休息,直到其餘線程釋放資源喚醒本身,本身成功拿到相應量的資源後才返回。下面是doAcquireShared()的源碼:
private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); //——加入隊列尾部 boolean failed = true; //——是否成功標誌 try { boolean interrupted = false; //——等待過程當中是否被中斷的標誌 for (;;) { final Node p = node.predecessor(); //前驅 if (p == head) { //若是到head的下一個,由於head是拿到資源的線程,此時node被喚醒,極可能是head用完資源來喚醒本身 int r = tryAcquireShared(arg); //嘗試獲取資源 if (r >= 0) { //成功 setHeadAndPropagate(node, r); //將head指向本身,還有剩餘資源能夠再喚醒以後的線程 p.next = null; // help GC if (interrupted) //若是等待過程當中被打斷過,直接將中斷補上 selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && //判斷狀態,尋找安全點,進入waiting狀態,等被unpark()或interrupt() parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
有木有以爲跟acquireQueued()很類似?對,其實流程並無太大區別。只不過這裏將補中斷的selfInterrupt()放到doAcquireShared()裏了,而獨佔模式是放到acquireQueued()以外,其實都同樣,不知道Doug Lea是怎麼想的。
跟獨佔模式比,還有一點須要注意的是,這裏只有線程是head.next時(「老二」),纔會去嘗試獲取資源,有剩餘的話還會喚醒以後的隊友。那麼問題就來了,假如老大用完後釋放了5個資源,而老二須要6個,老三須要1個,老四須要2個。老大先喚醒老二,老二一看資源不夠,他是把資源讓給老三呢,仍是不讓?答案是否認的!老二會繼續park()等待其餘線程釋放資源,也更不會去喚醒老三和老四了。獨佔模式,同一時刻只有一個線程去執行,這樣作何嘗不可;但共享模式下,多個線程是能夠同時執行的,如今由於老二的資源需求量大,而把後面量小的老三和老四也都卡住了。固然,這並非問題,只是AQS保證嚴格按照入隊順序喚醒罷了(保證公平,但下降了併發)。
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node); //head指向本身 //若是還有剩餘資源,繼續喚醒下一個鄰居線程 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; if (s == null || s.isShared()) doReleaseShared(); } }
此方法在setHead()的基礎上多了一步,就是本身甦醒的同時,若是條件符合(好比還有剩餘資源),還會去喚醒後繼結點,畢竟是共享模式!
OK,至此,acquireShared()也要告一段落了。讓咱們再梳理一下它的流程:
- tryAcquireShared()嘗試獲取資源,成功則直接返回;
- 失敗則經過doAcquireShared()進入等待隊列park(),直到被unpark()/interrupt()併成功獲取到資源才返回。整個等待過程也是忽略中斷的。
其實跟acquire()的流程大同小異,只不過多了個本身拿到資源後,還會去喚醒後繼隊友的操做(這纔是共享嘛)。
上一小節已經把acquireShared()說完了,這一小節就來說講它的反操做releaseShared()吧。此方法是共享模式下線程釋放共享資源的頂層入口。它會釋放指定量的資源,若是成功釋放且容許喚醒等待線程,它會喚醒等待隊列裏的其餘線程來獲取資源。下面是releaseShared()的源碼:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { //嘗試釋放資源 doReleaseShared(); //喚醒後繼結點 return true; } return false; }
此方法的流程也比較簡單,一句話:釋放掉資源後,喚醒後繼。跟獨佔模式下的release()類似,但有一點稍微須要注意:獨佔模式下的tryRelease()在徹底釋放掉資源(state=0)後,纔會返回true去喚醒其餘線程,這主要是基於獨佔下可重入的考量;而共享模式下的releaseShared()則沒有這種要求,共享模式實質就是控制必定量的線程併發執行,那麼擁有資源的線程在釋放掉部分資源時就能夠喚醒後繼等待結點。例如,資源總量是13,A(5)和B(7)分別獲取到資源併發運行,C(4)來時只剩1個資源就須要等待。A在運行過程當中釋放掉2個資源量,而後tryReleaseShared(2)返回true喚醒C,C一看只有3個仍不夠繼續等待;隨後B又釋放2個,tryReleaseShared(2)返回true喚醒C,C一看有5個夠本身用了,而後C就能夠跟A和B一塊兒運行。而ReentrantReadWriteLock讀鎖的tryReleaseShared()只有在徹底釋放掉資源(state=0)才返回true,因此自定義同步器能夠根據須要決定tryReleaseShared()的返回值。
此方法主要用於喚醒後繼。下面是它的源碼:
private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue;// loop to recheck cases unparkSuccessor(h); //喚醒後繼 } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) //head發生變化 break; } }
本節咱們詳解了獨佔和共享兩種模式下獲取-釋放資源(acquire-release、acquireShared-releaseShared)的源碼,相信你們都有必定認識了。值得注意的是,acquire()和acquireShared()兩種方法下,線程在等待隊列中都是忽略中斷的。AQS也支持響應中斷的,acquireInterruptibly()/acquireSharedInterruptibly()便是,這裏相應的源碼跟acquire()和acquireShared()差很少,這裏就再也不詳解了。
Mutex是一個不可重入的互斥鎖實現。鎖資源(AQS裏的state)只有兩種狀態:0表示未鎖定,1表示鎖定。下邊是Mutex的核心源碼:
class Mutex implements Lock, java.io.Serializable { // 自定義同步器 private static class Sync extends AbstractQueuedSynchronizer { // 判斷是否鎖定狀態 protected boolean isHeldExclusively() { return getState() == 1; } // 嘗試獲取資源,當即返回。成功則返回true,不然false。 public boolean tryAcquire(int acquires) { assert acquires == 1; // 這裏限定只能爲1個量 if (compareAndSetState(0, 1)) {//state爲0才設置爲1,不可重入! setExclusiveOwnerThread(Thread.currentThread());//設置爲當前線程獨佔資源 return true; } return false; } // 嘗試釋放資源,當即返回。成功則爲true,不然false。 protected boolean tryRelease(int releases) { assert releases == 1; // 限定爲1個量 if (getState() == 0)//既然來釋放,那確定就是已佔有狀態了。只是爲了保險,多層判斷! throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0);//釋放資源,放棄佔有狀態 return true; } } // 真正同步類的實現都依賴繼承於AQS的自定義同步器! private final Sync sync = new Sync(); //lock<-->acquire。二者語義同樣:獲取資源,即使等待,直到成功才返回。 public void lock() { sync.acquire(1); } //tryLock<-->tryAcquire。二者語義同樣:嘗試獲取資源,要求當即返回。成功則爲true,失敗則爲false。 public boolean tryLock() { return sync.tryAcquire(1); } //unlock<-->release。二者語文同樣:釋放資源。 public void unlock() { sync.release(1); } //鎖是否佔有狀態 public boolean isLocked() { return sync.isHeldExclusively(); } }
同步類在實現時通常都將自定義同步器(sync)定義爲內部類,供本身使用;而同步類本身(Mutex)則實現某個接口,對外服務。固然,接口的實現要直接依賴sync,它們在語義上也存在某種對應關係!!而sync只用實現資源state的獲取-釋放方式tryAcquire-tryRelelase,至於線程的排隊、等待、喚醒等,上層的AQS都已經實現好了,咱們不用關心。
除了Mutex,ReentrantLock/CountDownLatch/Semphore這些同步類的實現方式都差很少,不一樣的地方就在獲取-釋放資源的方式tryAcquire-tryRelelase。掌握了這點,AQS的核心便被攻破了!
OK,至此,整個AQS的講解也要落下帷幕了。
Java併發編程之美