多线程—AQS独占锁与共享锁原理
java.util.concurrent.locks包下,包含了多種鎖,ReentrantLock獨(dú)占鎖、ReentrantReadWriteLock讀寫鎖等,還有java.util.concurrent下的Semaphore、CountDownLatch都是基于AQS實(shí)現(xiàn)的。
AQS是一個(gè)抽象類,但事實(shí)上它并不包含任何抽象方法。AQS將一些需要子類覆寫的方法都設(shè)計(jì)成protect方法,將其默認(rèn)實(shí)現(xiàn)為拋出UnsupportedOperationException異常。如果子類使用到這些方法,但是沒有覆寫,則會拋出異常;如果子類沒有使用到這些方法,則不需要做任何操作。
可重寫方法:
- tryAcquire(int) 嘗試獲取鎖
- tryRelease(int) 嘗試釋放鎖
- tryAcquireShared(int) 共享的方式嘗試獲取鎖
- tryReleaseShared(int) 共享的方式嘗試釋放鎖
- isHeldExclusively() 判斷當(dāng)前是否為獨(dú)占鎖
final方法:
- getState
- setState(int)
- compareAndSetState(int, int)?
- setExclusiveOwnerThread(Thread.currentThread())? 將該線程設(shè)置為當(dāng)前鎖的持有者
根據(jù)實(shí)現(xiàn)方式的不同,可以分為兩種:獨(dú)占鎖和共享鎖
- 獨(dú)占鎖:ReentrantLock
- 共享鎖:CountDownLatch、CyclicBarrier、Semaphore
- ReentrantReadWriteLock寫的時(shí)候是獨(dú)占鎖,讀的時(shí)候是共享鎖
使用方法:
推薦作為靜態(tài)內(nèi)部類來繼承AQS。例如ReentrantLock作為外部類實(shí)現(xiàn)Lock接口,靜態(tài)抽象內(nèi)部類Sync繼承AQS。重寫Lock接口方法時(shí),是直接調(diào)用Sync類的實(shí)現(xiàn)類公平鎖NonfairSync或非公平鎖FairSync內(nèi)的方法來完成相應(yīng)邏輯。
- 公平鎖:線程獲取鎖的順序和調(diào)用lock的順序一樣,FIFO;
- 非公平鎖:線程獲取鎖的順序和調(diào)用lock的順序無關(guān),全憑運(yùn)氣。
AQS的3部分:
state:
private?volatile?int?state; //volatile,state為0表示鎖沒有被占用,可以把state變量當(dāng)做是當(dāng)前持有該鎖的線程數(shù)量。隊(duì)列:
一個(gè)FIFO的雙向鏈表,這種結(jié)構(gòu)的特點(diǎn)是每個(gè)數(shù)據(jù)結(jié)構(gòu)都有兩個(gè)指針,分別指向直接的后繼節(jié)點(diǎn)和直接前驅(qū)節(jié)點(diǎn)。所以雙向鏈表可以從任意一個(gè)節(jié)點(diǎn)開始很方便的訪問前驅(qū)和后繼。
每個(gè)Node其實(shí)是由線程封裝,當(dāng)線程爭搶鎖失敗后會封裝成Node加入到隊(duì)列中去。
Node主要屬性:
// 節(jié)點(diǎn)所代表的線程 volatile Thread thread;// 雙向鏈表,每個(gè)節(jié)點(diǎn)需要保存自己的前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)的引用 volatile Node prev; volatile Node next;// 線程所處的等待鎖的狀態(tài),初始化時(shí),該值為0 volatile int waitStatus; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3;// 該屬性用于條件隊(duì)列或者共享鎖 Node nextWaiter;- CANCELLED:值為1,在同步隊(duì)列中等待的線程等待超時(shí)或被中斷,需要從同步隊(duì)列中取消該Node的結(jié)點(diǎn),其結(jié)點(diǎn)的waitStatus為CANCELLED,即結(jié)束狀態(tài),進(jìn)入該狀態(tài)后的結(jié)點(diǎn)將不會再變化。
- SIGNAL:值為-1,被標(biāo)識為該等待喚醒狀態(tài)的后繼結(jié)點(diǎn),當(dāng)其前繼結(jié)點(diǎn)的線程釋放了同步鎖或被取消,將會通知該后繼結(jié)點(diǎn)的線程執(zhí)行。說白了,就是處于喚醒狀態(tài),只要前繼結(jié)點(diǎn)釋放鎖,就會通知標(biāo)識為SIGNAL狀態(tài)的后繼結(jié)點(diǎn)的線程執(zhí)行。
- CONDITION:值為-2,與Condition相關(guān),該標(biāo)識的結(jié)點(diǎn)處于等待隊(duì)列中,結(jié)點(diǎn)的線程等待在Condition上,當(dāng)其他線程調(diào)用了Condition的signal()方法后,CONDITION狀態(tài)的結(jié)點(diǎn)將從等待隊(duì)列轉(zhuǎn)移到同步隊(duì)列中,等待獲取同步鎖。
- PROPAGATE:值為-3,與共享模式相關(guān),在共享模式中,該狀態(tài)標(biāo)識結(jié)點(diǎn)的線程處于可運(yùn)行狀態(tài)。
隊(duì)列的頭節(jié)點(diǎn)和尾節(jié)點(diǎn)?
// 頭節(jié)點(diǎn),不代表任何線程,是一個(gè)啞結(jié)點(diǎn) private transient volatile Node head;// 尾節(jié)點(diǎn),每一個(gè)請求鎖的線程會加到隊(duì)尾 private transient volatile Node tail;CAS(Compare and Swap)操作:
JAVA使用?鎖和循環(huán)CAS來實(shí)現(xiàn)原子操作,原子操作意為”不可被中斷的一個(gè)或一系列操作”,保證只有一個(gè)線程操作數(shù)據(jù)。
悲觀鎖與樂觀鎖:
- 悲觀鎖: 假定會發(fā)生并發(fā)沖突,所以當(dāng)某個(gè)線程獲取共享資源時(shí),會阻止別的線程獲取共享資源。也稱獨(dú)占鎖或者互斥鎖,例如synchronized同步鎖。
- 樂觀鎖: 假設(shè)不會發(fā)生并發(fā)沖突,只有在最后更新共享資源的時(shí)候會判斷一下在此期間有沒有別的線程修改了這個(gè)共享資源。如果發(fā)生沖突就重試,直到?jīng)]有沖突,更新成功。CAS就是一種樂觀鎖實(shí)現(xiàn)方式。
CAS的思想很簡單:三個(gè)參數(shù),一個(gè)當(dāng)前內(nèi)存值V、舊的預(yù)期值A(chǔ)、即將更新的值B,當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值修改為B并返回true,否則什么都不做,并返回false。循環(huán)CAS實(shí)現(xiàn)的基本思路就是循環(huán)進(jìn)行CAS操作直到成功為止。
Java中的CAS功能是通過Unsafe類來實(shí)現(xiàn)的。Java并發(fā)包中java.util.concurrent 大量使用了這種操作,來保證線程安全。compareAndSetState(int, int)方法就是通過Unsafe實(shí)現(xiàn)的,而setState就是線程不安全的。
除了try*()方法外,AQS自身實(shí)現(xiàn)了諸多的如acquire和doAcquire()等方法,他們之間的區(qū)別在于try*()方法代表一次嘗試性的獲取鎖操作,如果獲取到了就拿到了鎖,否則直接返回。而AQS自身實(shí)現(xiàn)的acquire和doAcquire()等方法如果獲取不到鎖會能夠進(jìn)入同步/等待隊(duì)列中阻塞等待進(jìn)行鎖的爭奪,直到拿到了鎖返回。對于共享鎖,try*()也會進(jìn)行自旋獲取,因?yàn)楣蚕礞i可以被多個(gè)線程持有。
下面通過acquire方法來分析AQS怎么獲取鎖:
public final void acquire(int arg) {if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt(); }tryAcquire是子類獲取鎖應(yīng)該實(shí)現(xiàn)的方法,一般在里面判斷state值,state==0時(shí)就可以獲取到鎖,通過下面方法獲取鎖,然后返回true,否則返回false,公平鎖的話會再判斷自己有沒有前驅(qū)節(jié)點(diǎn)等。
setExclusiveOwnerThread(current); // 將當(dāng)前線程設(shè)置為占用鎖的線程當(dāng)tryAcquire獲取鎖失敗時(shí),&&前面為true,才會調(diào)用addWaiter方法,將當(dāng)前線程包裝成Node,加到等待鎖的隊(duì)列中去, 因?yàn)槭荈IFO隊(duì)列, 所以加在隊(duì)尾。
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode); //將當(dāng)前線程包裝成Node// 這里我們用注釋的形式把Node的構(gòu)造函數(shù)貼出來// 因?yàn)閭魅氲膍ode值為Node.EXCLUSIVE,所以節(jié)點(diǎn)的nextWaiter屬性被設(shè)為null/*static final Node EXCLUSIVE = null;Node(Thread thread, Node mode) { // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}*/Node pred = tail;// 如果隊(duì)列不為空, 則用CAS方式將當(dāng)前節(jié)點(diǎn)設(shè)為尾節(jié)點(diǎn)if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node); //將節(jié)點(diǎn)插入隊(duì)列return node; }將一個(gè)節(jié)點(diǎn)node添加到sync queue末尾的三步:
添加有可能失敗,原因可能是以下兩種之一:
失敗的時(shí)候會調(diào)用一個(gè)enq(node)方法,在該方法中, 出現(xiàn)第一種情況時(shí),該方法也負(fù)責(zé)在隊(duì)列為空時(shí), 初始化隊(duì)列,然后使用了死循環(huán), 即以自旋方式將節(jié)點(diǎn)插入隊(duì)列,如果失敗則不停的嘗試, 直到成功為止,運(yùn)用到了樂觀鎖的原理。
private Node enq(final Node node) {for (;;) {Node t = tail;// 如果是空隊(duì)列, 首先進(jìn)行初始化// 這里也可以看出, 隊(duì)列不是在構(gòu)造的時(shí)候初始化的, 而是延遲到需要用的時(shí)候再初始化, 以提升性能if (t == null) { // 注意,初始化時(shí)使用new Node()方法新建了一個(gè)dummy節(jié)點(diǎn)if (compareAndSetHead(new Node()))tail = head; // 這里僅僅是將尾節(jié)點(diǎn)指向dummy節(jié)點(diǎn),并沒有返回} else {// 到這里說明隊(duì)列已經(jīng)不是空的了, 這個(gè)時(shí)候再繼續(xù)嘗試將節(jié)點(diǎn)加到隊(duì)尾node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}} }enq(node)方法運(yùn)行可能會造成尾分叉既多個(gè)尾節(jié)點(diǎn)的現(xiàn)象,因?yàn)閚ode.prev = t;可能被多個(gè)線程運(yùn)行,后面if語句則是CAS操作保證了單線程運(yùn)行。不過只是一種暫時(shí)的現(xiàn)象,因?yàn)榫€程不斷循環(huán)保證入隊(duì)。
enq(node)方法后返回到acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法。
該方法中將再次嘗試去獲取鎖,因?yàn)槿绻?dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)就是HEAD節(jié)點(diǎn),則可以再嘗試獲取鎖。
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {// 該方法用來查找并獲取前置節(jié)點(diǎn)。final Node p = node.predecessor();// 在當(dāng)前節(jié)點(diǎn)的前驅(qū)就是HEAD節(jié)點(diǎn)時(shí), 再次嘗試獲取鎖if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}//在獲取鎖失敗后, 判斷是否需要把當(dāng)前線程掛起if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);} }setHead方法將該節(jié)點(diǎn)設(shè)置成頭節(jié)點(diǎn),上一個(gè)頭節(jié)點(diǎn)就被頂?shù)簟R源藖磉_(dá)成節(jié)點(diǎn)出隊(duì)列的效果。
private void setHead(Node node) {head = node;node.thread = null;node.prev = null; }如果獲取不到鎖調(diào)用shouldParkAfterFailedAcquire,該方法用于決定在獲取鎖失敗后, 是否將線程掛起,決定的依據(jù)就是前驅(qū)節(jié)點(diǎn)的waitStatus值。在獨(dú)占鎖的獲取操作中,我們只用到了其中的兩個(gè)——CANCELLED和SIGNAL。每一個(gè)節(jié)點(diǎn)最開始的時(shí)候waitStatus的值都被初始化為0。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus; // 獲得前驅(qū)節(jié)點(diǎn)的wsif (ws == Node.SIGNAL)// 前驅(qū)節(jié)點(diǎn)的狀態(tài)已經(jīng)是SIGNAL了,說明鬧鐘已經(jīng)設(shè)了,可以直接睡了return true;if (ws > 0) {// 當(dāng)前節(jié)點(diǎn)的 ws > 0, 則為 Node.CANCELLED 說明前驅(qū)節(jié)點(diǎn)已經(jīng)取消了等待鎖(由于超時(shí)或者中斷等原因)// 既然前驅(qū)節(jié)點(diǎn)不等了, 那就繼續(xù)往前找, 直到找到一個(gè)還在等待鎖的節(jié)點(diǎn)// 然后我們跨過這些不等待鎖的節(jié)點(diǎn), 直接排在等待鎖的節(jié)點(diǎn)的后面do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 前驅(qū)節(jié)點(diǎn)的狀態(tài)既不是SIGNAL,也不是CANCELLED// 用CAS設(shè)置前驅(qū)節(jié)點(diǎn)的ws為 Node.SIGNAL,給自己定一個(gè)鬧鐘compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false; }- 如果為前驅(qū)節(jié)點(diǎn)的waitStatus值為 Node.SIGNAL 則直接返回 true運(yùn)行&&后面的parkAndCheckInterrupt()方法。
- 如果為前驅(qū)節(jié)點(diǎn)的waitStatus值為 Node.CANCELLED (ws > 0), 則跳過那些節(jié)點(diǎn), 重新尋找正常等待中的前驅(qū)節(jié)點(diǎn),然后排在它后面,返回false,繼續(xù)循環(huán)。
- 其他情況, 將前驅(qū)節(jié)點(diǎn)的狀態(tài)改為 Node.SIGNAL, 返回false,繼續(xù)循環(huán)。
返回false時(shí)會進(jìn)行循環(huán),就是將那些CANCELLED的節(jié)點(diǎn)移出隊(duì)列,然后再循環(huán)一次,再嘗試獲取鎖,因?yàn)樽约河锌赡芤呀?jīng)到頭節(jié)點(diǎn)后面了,如果不是則自己排到waitStatus值為SIGNAL的前節(jié)點(diǎn)后面,此時(shí)shouldParkAfterFailedAcquire返回true。將調(diào)用parkAndCheckInterrupt()方法。
private final boolean parkAndCheckInterrupt() {LockSupport.park(this); // 線程被掛起,停在這里不再往下執(zhí)行了return Thread.interrupted(); }調(diào)用了LockSupport類的park方法。
LockSupport工具類主要用來掛起park(Thread)和喚醒unpark(Thread)線程,底層實(shí)現(xiàn)也是使用的Unsafe類。若其他線程調(diào)用了阻塞線程的interrupt()方法,阻塞線程也會返回,即阻塞的線程是響應(yīng)中斷的,而且不會拋出InterruptedException異常。LockSupport并不需要獲取對象的監(jiān)視器,而是給線程一個(gè)“許可”(permit),unpark可以先于park調(diào)用,unpark一個(gè)并沒有被park的線程時(shí),該線程在下一次調(diào)用park方法時(shí)就不會被掛起。
所以最后return Thread.interrupted();是因?yàn)椴荒鼙WC他不是被中斷的,所以返回Thread的中斷狀態(tài)。
鎖的釋放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(arg)? ?
該方法由繼承AQS的子類實(shí)現(xiàn), 為釋放鎖的具體邏輯。一般是將state設(shè)置為0,setExclusiveOwnerThread(null);再將當(dāng)前線程設(shè)置為null。
unparkSuccessor(h)? ?喚醒h的后繼線程
當(dāng)有頭節(jié)點(diǎn)且頭節(jié)點(diǎn)的waitStatus不等于0的時(shí)候則喚醒后繼線程,因?yàn)閣aitStatus初始值為0,當(dāng)隊(duì)列進(jìn)入新節(jié)點(diǎn)時(shí),頭節(jié)點(diǎn)會被設(shè)置為SIGNAL。
private void unparkSuccessor(Node node) {int ws = node.waitStatus;// 如果head節(jié)點(diǎn)的ws比0小, 則直接將它設(shè)為0if (ws < 0)compareAndSetWaitStatus(node, ws, 0);// 此時(shí)從尾節(jié)點(diǎn)開始向前找起, 直到找到距離head節(jié)點(diǎn)最近的ws<=0的節(jié)點(diǎn)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)s = t; // 沒有return, 繼續(xù)向前找。}// 如果找到了還在等待鎖的節(jié)點(diǎn),則喚醒它if (s != null)LockSupport.unpark(s.thread); }從尾部開始遍歷,因?yàn)樾鹿?jié)點(diǎn)接入的時(shí)候是先node.prev = t,隊(duì)列可能只執(zhí)行到這步,后面還沒執(zhí)行,所以從尾向前遍歷。
找到頭節(jié)點(diǎn)的下一個(gè)不是CANCELLED的節(jié)點(diǎn)并喚醒,unpark方法對應(yīng)前面添加節(jié)點(diǎn)的park方法,所以回到前面。
private final boolean parkAndCheckInterrupt() {LockSupport.park(this); // 線程被掛起,停在這里不再往下執(zhí)行了return Thread.interrupted(); }所以當(dāng)線程獲取不到鎖,會被park一直阻塞狀態(tài),直到被interrupt或者有鎖的線程釋放鎖時(shí),才會獲得鎖。獲得鎖后返回中斷狀態(tài)
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {// 該方法用來查找并獲取前置節(jié)點(diǎn)。final Node p = node.predecessor();// 在當(dāng)前節(jié)點(diǎn)的前驅(qū)就是HEAD節(jié)點(diǎn)時(shí), 再次嘗試獲取鎖if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}//在獲取鎖失敗后, 判斷是否需要把當(dāng)前線程掛起if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);} }如果是中斷喚醒的返回true,再設(shè)置interrupted = true,因?yàn)門hread.interrupted()調(diào)用后中斷狀態(tài)會被重新設(shè)回false。繼續(xù)循環(huán),如果自己是在頭節(jié)點(diǎn)下一位,就可以獲取鎖了,否則又要掛起。
共享鎖
共享鎖的acquireShared方法對應(yīng)獨(dú)占鎖acquire
public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)doAcquireShared(arg); } public final void acquire(int arg) {if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt(); }doAcquireShared對應(yīng)了獨(dú)占鎖的acquireQueued,
private void doAcquireShared(int arg) { ··························································final Node node = addWaiter(Node.SHARED); //代表共享模式 ··························································boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();//與獨(dú)占鎖的acquireQueued的區(qū)別主要就是中間這段代碼 ··························································if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {setHeadAndPropagate(node, r);p.next = null; // help GCif (interrupted)selfInterrupt();failed = false;return;}}··························································if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);} }重點(diǎn)在setHeadAndPropagate方法
private void setHeadAndPropagate(Node node, int propagate) {Node h = head; // Record old head for check belowsetHead(node);//if里面包含了兩個(gè)頭節(jié)點(diǎn),一個(gè)新一個(gè)老,多線程下兩者可能不一樣,兩種情況。 //1.propagate > 0 表示調(diào)用方指明了后繼節(jié)點(diǎn)需要被喚醒 //2.頭節(jié)點(diǎn)后面的節(jié)點(diǎn)需要被喚醒(waitStatus<0),不論是老的頭結(jié)點(diǎn)還是新的頭結(jié)點(diǎn)if (propagate > 0 || h == null || h.waitStatus < 0 ||(h = head) == null || h.waitStatus < 0) {Node s = node.next;//如果當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)是共享類型或者沒有后繼節(jié)點(diǎn),則進(jìn)行喚醒 //這里可以理解為除非明確指明不需要喚醒(后繼等待節(jié)點(diǎn)是獨(dú)占類型),否則都要喚醒if (s == null || s.isShared())doReleaseShared();} }在setHeadAndPropagate方法里面,將獲取鎖的節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn),然后再去doReleaseShared,doReleaseShared對應(yīng)了獨(dú)占鎖的unparkSuccessor,作用是喚醒下一個(gè)線程,所以在共享鎖的releaseShared方法(對應(yīng)獨(dú)占鎖release),就是釋放鎖方法里,也主要是用doReleaseShared來釋放鎖。
獨(dú)占鎖與共享鎖的區(qū)別
- 獨(dú)占鎖是持有鎖的線程釋放鎖之后才會去喚醒下一個(gè)線程。
- 共享鎖是線程獲取到鎖后,就會去喚醒下一個(gè)線程,所以共享鎖在獲取鎖和釋放鎖的時(shí)候都會調(diào)用doReleaseShared方法喚醒下一個(gè)線程,當(dāng)然這會收共享線程數(shù)量的限制。
下面到doReleaseShared方法
private void doReleaseShared() {for (;;) {Node h = head;if (h != null && h != tail) { //至少有頭尾兩個(gè)節(jié)點(diǎn)int ws = h.waitStatus;if (ws == Node.SIGNAL) { //ws為SIGNAL的時(shí)候才去喚醒下一個(gè)節(jié)點(diǎn)if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) //將頭節(jié)點(diǎn)的SIGNAL改為0,CAS操作continue; // loop to recheck casesunparkSuccessor(h); //保證單線程運(yùn)行喚醒后繼線程}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS}if (h == head) // loop if head changedbreak;} }doReleaseShared方法里,我們暫且把現(xiàn)在持有鎖的線程成為節(jié)點(diǎn)A,下一個(gè)節(jié)點(diǎn)為B。
首先判斷?if (ws == Node.SIGNAL),因?yàn)槲覀兠看尾迦牍?jié)點(diǎn)都會默認(rèn)0,并且把前節(jié)點(diǎn)設(shè)成SIGNAL,所以當(dāng)條件成立時(shí),聲明A節(jié)點(diǎn)后面已經(jīng)有B了。 到下一層,if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)),意思是把節(jié)點(diǎn)A的SIGNAL改為0。
為什么需要CAS操作呢?
CAS保證了后面單線程喚醒后繼線程的操作,在上面談到的doReleaseShared這個(gè)方法,在獲取鎖和釋放鎖的時(shí)候都會調(diào)用,防止重復(fù)喚醒。
接下來是 else if (ws == 0 &&?!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))? 什么時(shí)候滿足這句呢?
ws為0,每當(dāng)一個(gè)節(jié)點(diǎn)進(jìn)同步隊(duì)列都會把前面節(jié)點(diǎn)設(shè)置為SIGNAL,自己初始為0,所以滿足ws==0的條件就是節(jié)點(diǎn)A是隊(duì)列最后一個(gè)且后面還沒有節(jié)點(diǎn)B入列的情況。
滿足了ws==0,運(yùn)行下面這句。
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE),CAS操作失敗返回true,將該新節(jié)點(diǎn)A的0設(shè)置APROPAGATE不成功。不成功就意味著新節(jié)點(diǎn)A的0已經(jīng)被改了,被改意味著新節(jié)點(diǎn)A后面已經(jīng)進(jìn)入了節(jié)點(diǎn)B,設(shè)置前節(jié)點(diǎn)為SIGNAL的操作是線程在獲取不到鎖之后,阻塞之前,忘記的可以回顧一下前面的內(nèi)容。
所以else if (ws == 0 &&?!compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) 這個(gè)條件成立分為了兩個(gè)階段,既有尾節(jié)點(diǎn)又加入新節(jié)點(diǎn)的這個(gè)瞬間可能會滿足。
那么滿足為什么要continue呢?
因?yàn)楣?jié)點(diǎn)B線程在獲取不到鎖之后,阻塞之前,所以此時(shí)A還沒釋放鎖,A仍是頭節(jié)點(diǎn),h==head條件成立,執(zhí)行break跳出循環(huán),不會去喚醒B了,這不符合共享鎖的機(jī)制。所以應(yīng)該continue繼續(xù)循環(huán),去喚醒B節(jié)點(diǎn),而不是等A運(yùn)行完釋放鎖的時(shí)候才去調(diào)用。
h == head如果不成立,說明A喚醒完B,B已經(jīng)調(diào)用了setHead這個(gè)方法了,這個(gè)時(shí)候再去循環(huán)看看B節(jié)點(diǎn)后面有沒有節(jié)點(diǎn)。
?
總結(jié)
以上是生活随笔為你收集整理的多线程—AQS独占锁与共享锁原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编译器vs.代码 谁之过
- 下一篇: 最易忽视的肾虚4件事