摘要:之所以使用這種方式是因為在恢復(fù)一個被掛起的線程與該線程真正運行之間存在著嚴(yán)重的延遲。這樣我們就可以把線程恢復(fù)運行的這段時間給利用起來了,結(jié)果就是線程更早的獲取了鎖,線程獲取鎖的時刻也沒有推遲。
前言
系列文章目錄
上一篇 我們學(xué)習(xí)了lock接口,本篇我們就以ReentrantLock為例,學(xué)習(xí)一下Lock鎖的基本的實現(xiàn)。我們先來看看Lock接口中的方法與ReentrantLock對其實現(xiàn)的對照表:
Lock 接口 | ReentrantLock 實現(xiàn) |
---|---|
lock() | sync.lock() |
lockInterruptibly() | sync.acquireInterruptibly(1) |
tryLock() | sync.nonfairTryAcquire(1) |
tryLock(long time, TimeUnit unit) | sync.tryAcquireNanos(1, unit.toNanos(timeout)) |
unlock() | sync.release(1) |
newCondition() | sync.newCondition() |
從表中可以看出,ReentrantLock對于Lock接口的實現(xiàn)都是直接“轉(zhuǎn)交”給sync對象的。
核心屬性ReentrantLock只有一個sync屬性,別看只有一個屬性,這個屬性提供了所有的實現(xiàn),我們上面介紹ReentrantLock對Lock接口的實現(xiàn)的時候就說到,它對所有的Lock方法的實現(xiàn)都調(diào)用了sync的方法,這個sync就是ReentrantLock的屬性,它繼承了AQS.
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer { abstract void lock(); //... }
在Sync類中,定義了一個抽象方法lock,該方法應(yīng)當(dāng)由繼承它的子類來實現(xiàn),關(guān)于繼承它的子類,我們在下一節(jié)分析構(gòu)造函數(shù)時再看。
構(gòu)造函數(shù)ReentrantLock共有兩個構(gòu)造函數(shù):
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
默認(rèn)的構(gòu)造函數(shù)使用了非公平鎖,另外一個構(gòu)造函數(shù)通過傳入一個boolean類型的fair變量來決定使用公平鎖還是非公平鎖。其中,F(xiàn)airSync和NonfairSync的定義如下:
static final class FairSync extends Sync { final void lock() {//省略實現(xiàn)} protected final boolean tryAcquire(int acquires) {//省略實現(xiàn)} } static final class NonfairSync extends Sync { final void lock() {//省略實現(xiàn)} protected final boolean tryAcquire(int acquires) {//省略實現(xiàn)} }
這里為什么默認(rèn)創(chuàng)建的是非公平鎖呢?因為非公平鎖的效率高呀,當(dāng)一個線程請求非公平鎖時,如果在發(fā)出請求的同時該鎖變成可用狀態(tài),那么這個線程會跳過隊列中所有的等待線程而獲得鎖。有的同學(xué)會說了,這不就是插隊嗎?
沒錯,這就是插隊!這也就是為什么它被稱作非公平鎖。
之所以使用這種方式是因為:
在恢復(fù)一個被掛起的線程與該線程真正運行之間存在著嚴(yán)重的延遲。
在公平鎖模式下,大家講究先來后到,如果當(dāng)前線程A在請求鎖,即使現(xiàn)在鎖處于可用狀態(tài),它也得在隊列的末尾排著,這時我們需要喚醒排在等待隊列隊首的線程H(在AQS中其實是次頭節(jié)點),由于恢復(fù)一個被掛起的線程并且讓它真正運行起來需要較長時間,那么這段時間鎖就處于空閑狀態(tài),時間和資源就白白浪費了,非公平鎖的設(shè)計思想就是將這段白白浪費的時間利用起來——由于線程A在請求鎖的時候本身就處于運行狀態(tài),因此如果我們此時把鎖給它,它就會立即執(zhí)行自己的任務(wù),因此線程A有機(jī)會在線程H完全喚醒之前獲得、使用以及釋放鎖。這樣我們就可以把線程H恢復(fù)運行的這段時間給利用起來了,結(jié)果就是線程A更早的獲取了鎖,線程H獲取鎖的時刻也沒有推遲。因此提高了吞吐量。
當(dāng)然,非公平鎖僅僅是在當(dāng)前線程請求鎖,并且鎖處于可用狀態(tài)時有效,當(dāng)請求鎖時,鎖已經(jīng)被其他線程占有時,就只能還是老老實實的去排隊了。
無論是非公平鎖的實現(xiàn)NonfairSync還是公平鎖的實現(xiàn)FairSync,它們都覆寫了lock方法和tryAcquire方法,這兩個方法都將用于獲取一個鎖。
Lock接口方法實現(xiàn) lock() 公平鎖實現(xiàn)關(guān)于ReentrantLock對于lock方法的公平鎖的實現(xiàn)邏輯,我們在逐行分析AQS源碼(1)——獨占鎖的獲取中已經(jīng)講過了,這里不再贅述。如果你還沒有看過那篇文章或者還不了解AQS,建議先去看一下那一篇文章,然后再讀下文。
非公平鎖實現(xiàn)接下來我們看看非公平鎖的實現(xiàn)邏輯:
// NonfairSync中的lock方法 final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
對比公平鎖中的lock方法:
// FairSync中的lock方法 final void lock() { acquire(1); }
可見,相比公平鎖,非公平鎖在當(dāng)前鎖沒有被占用時,可以直接嘗試去獲取鎖,而不用排隊,所以它在一開始就嘗試使用CAS操作去搶鎖,只有在該操作失敗后,才會調(diào)用AQS的acquire方法。
由于acquire方法中除了tryAcquire由子類實現(xiàn)外,其余都由AQS實現(xiàn),我們在前面的文章中已經(jīng)介紹的很詳細(xì)了,這里不再贅述,我們僅僅看一下非公平鎖的tryAcquire方法實現(xiàn):
// NonfairSync中的tryAcquire方法實現(xiàn) protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
它調(diào)用了Sync類的nonfairTryAcquire方法:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 只有這一處和公平鎖的實現(xiàn)不同,其它的完全一樣。 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
我們可以拿它和公平鎖的tryAcquire對比一下:
// FairSync中的tryAcquire方法實現(xiàn) protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
看見沒?這兩個方法幾乎一模一樣,唯一的區(qū)別就是非公平鎖在搶鎖時不再需要調(diào)用hasQueuedPredecessors方法先去判斷是否有線程排在自己前面,而是直接爭鎖,其它的完全和公平鎖一致。
lockInterruptibly()前面的lock方法是阻塞式的,搶到鎖就返回,搶不到鎖就將線程掛起,并且在搶鎖的過程中是不響應(yīng)中斷的(關(guān)于不響應(yīng)中斷,見這篇文章末尾的分析),lockInterruptibly提供了一種響應(yīng)中斷的方式,在ReentrantLock中,無論是公平鎖還是非公平鎖,這個方法的實現(xiàn)都是一樣的:
public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
他們都調(diào)用了AQS的acquireInterruptibly方法:
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }
該方法首先檢查當(dāng)前線程是否已經(jīng)被中斷過了,如果已經(jīng)被中斷了,則立即拋出InterruptedException(這一點是lockInterruptibly要求的,參見上一篇Lock接口的介紹)。
如果調(diào)用這個方法時,當(dāng)前線程還沒有被中斷過,則接下來先嘗試用普通的方法來獲取鎖(tryAcquire)。如果獲取成功了,則萬事大吉,直接就返回了;否則,與前面的lock方法一樣,我們需要將當(dāng)前線程包裝成Node扔進(jìn)等待隊列,所不同的是,這次,在隊列中嘗試獲取鎖時,如果發(fā)生了中斷,我們需要對它做出響應(yīng), 并拋出異常
private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return; //與acquireQueued方法的不同之處 } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); //與acquireQueued方法的不同之處 } } finally { if (failed) cancelAcquire(node); } }
如果你在上面分析lock方法的時候已經(jīng)理解了acquireQueued方法,那么再看這個方法就很輕松了,我們把lock方法中的acquireQueued拿出來和上面對比一下:
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; //不同之處 for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; //不同之處 } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; //不同之處 } } finally { if (failed) cancelAcquire(node); } }
通過代碼對比可以看出,doAcquireInterruptibly和acquireQueued(addWaiter(Node.EXCLUSIVE), arg))的調(diào)用本質(zhì)上講并無區(qū)別。只不過對于addWaiter(Node.EXCLUSIVE),一個是外部調(diào)用,通過參數(shù)傳進(jìn)來;一個是直接在方法內(nèi)部調(diào)用。所以這兩個方法的邏輯幾乎是一樣的,唯一的不同就是在doAcquireInterruptibly中,當(dāng)我們檢測到中斷后,不再是簡單的記錄中斷狀態(tài),而是直接拋出InterruptedException。
當(dāng)拋出中斷異常后,在返回前,我們將進(jìn)入finally代碼塊進(jìn)行善后工作,很明顯,此時failed是為true的,我們將調(diào)用cancelAcquire方法:
private void cancelAcquire(Node node) { // Ignore if node doesn"t exist if (node == null) return; node.thread = null; // 由當(dāng)前節(jié)點向前遍歷,跳過那些已經(jīng)被cancel的節(jié)點 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 從當(dāng)前節(jié)點向前開始查找,找到第一個waitStatus>0的Node, 該節(jié)點為pred // predNext即是pred節(jié)點的下一個節(jié)點 // 到這里可知,pred節(jié)點是沒有被cancel的節(jié)點,但是pred節(jié)點往后,一直到當(dāng)前節(jié)點Node都處于被Cancel的狀態(tài) Node predNext = pred.next; //將當(dāng)前節(jié)點的waitStatus的狀態(tài)設(shè)為Node.CANCELLED node.waitStatus = Node.CANCELLED; // 如果當(dāng)前節(jié)點是尾節(jié)點,則將之前找到的節(jié)點pred重新設(shè)置成尾節(jié)點,并將pred節(jié)點的next屬性由predNext修改成Null // 這一段本質(zhì)上是將pred節(jié)點后面的節(jié)點全部移出隊列,因為它們都被cancel掉了 if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { // 到這里說明當(dāng)前節(jié)點已經(jīng)不是尾節(jié)點了,或者設(shè)置新的尾節(jié)點失敗了 // 我們前面說過,并發(fā)條件下,什么都有可能發(fā)生 // 即在當(dāng)前線程運行這段代碼的過程中,其他線程可能已經(jīng)入隊了,成為了新的尾節(jié)點 // 雖然我們之前已經(jīng)將當(dāng)前節(jié)點的waitStatus設(shè)為了CANCELLED // 但是由我們在分析lock方法的文章可知,新的節(jié)點入隊后會設(shè)置鬧鐘,將找一個沒有CANCEL的前驅(qū)節(jié)點,將它的status設(shè)置成SIGNAL以喚醒自己。 // 所以,在當(dāng)前節(jié)點的后繼節(jié)點入隊后,可能將當(dāng)前節(jié)點的waitStatus修改成了SIGNAL // 而在這時,我們發(fā)起了中斷,又將這個waitStatus修改成CANCELLED // 所以在當(dāng)前節(jié)點出隊前,要負(fù)責(zé)喚醒后繼節(jié)點。 int ws; if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { unparkSuccessor(node); } node.next = node; // help GC } }
這個cancelAcquire方法不僅是取消了當(dāng)前節(jié)點的排隊,還會同時將當(dāng)前節(jié)點之前的那些已經(jīng)CANCEL掉的節(jié)點移出隊列。不過這里尤其需要注意的是,這里是在并發(fā)條件下,此時此刻,新的節(jié)點可能已經(jīng)入隊了,成為了新的尾節(jié)點,這將會導(dǎo)致node == tail && compareAndSetTail(node, pred)這一條件失敗。
這個函數(shù)的前半部分是就是基于當(dāng)前節(jié)點就是隊列的尾節(jié)點的,即在執(zhí)行這個函數(shù)時,沒有新的節(jié)點入隊,這部分的邏輯比較簡單,大家直接看代碼中的注釋解釋即可。
而后半部分是基于有新的節(jié)點加進(jìn)來,當(dāng)前節(jié)點已經(jīng)不再是尾節(jié)點的情況,我們詳細(xì)看看這else部分:
if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { int ws; if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); //將pred節(jié)點的后繼節(jié)點改為當(dāng)前節(jié)點的后繼節(jié)點 } else { unparkSuccessor(node); } node.next = node; // help GC }
(這里再說明一下pred變量所代表的含義:它表示了從當(dāng)前節(jié)點向前遍歷所找到的第一個沒有被cancel的節(jié)點。)
執(zhí)行到else代碼塊,則我們目前的狀況如下:
當(dāng)前線程被中斷了,我們已經(jīng)將它的Node的waitStatus屬性設(shè)為CANCELLED,thread屬性置為null
在執(zhí)行這個方法期間,又有其他線程加入到隊列中來,成為了新的尾節(jié)點,使得當(dāng)前線程已經(jīng)不是隊尾了
在這種情況下,我們將執(zhí)行if語句,將pred節(jié)點的后繼節(jié)點改為當(dāng)前節(jié)點的后繼節(jié)點(compareAndSetNext(pred, predNext, next)),即將從pred節(jié)點開始(不包含pred節(jié)點)一直到當(dāng)前節(jié)點(包括當(dāng)前節(jié)點)之間的所有節(jié)點全部移出隊列,因為他們都是被cancel的節(jié)點。當(dāng)然這是基于一定條件的,條件為:
pred節(jié)點不是頭節(jié)點
pred節(jié)點的thread不為null
pred節(jié)點的waitStatus屬性是SIGNAL或者是小于等于0但是被我們成功的設(shè)置成signal
上面這三個條件保證了pred節(jié)點確實是一個正在正常等待鎖的線程,并且它的waitStatus屬性為SIGNAL。
如果這一條件無法被滿足,那么我們將直接通過unparkSuccessor喚醒它的后繼節(jié)點。
到這里,我們總結(jié)一下cancelAcquire方法:
如果要cancel的節(jié)點已經(jīng)是尾節(jié)點了,則在我們后面并沒有節(jié)點需要喚醒,我們只需要從當(dāng)前節(jié)點(即尾節(jié)點)開始向前遍歷,找到所有已經(jīng)cancel的節(jié)點,將他們移出隊列即可
如果要cancel的節(jié)點后面還有別的節(jié)點,并且我們找到的pred節(jié)點處于正常等待狀態(tài),我們還是直接將從當(dāng)前節(jié)點開始,到pred節(jié)點直接的所有節(jié)點,全部移出隊列,這里并不需要喚醒當(dāng)前節(jié)點的后繼節(jié)點,因為它已經(jīng)接在了pred的后面,pred的waitStatus已經(jīng)被置為SIGNAL,它會負(fù)責(zé)喚醒后繼節(jié)點
如果上面的條件不滿足,按說明當(dāng)前節(jié)點往前已經(jīng)沒有在等待中的線程了,我們就直接將后繼節(jié)點喚醒。
有的同學(xué)就要問了,那第3條只是把當(dāng)前節(jié)點的后繼節(jié)點喚醒了,并沒有將當(dāng)前節(jié)點移除隊列呀?但是當(dāng)前節(jié)點已經(jīng)取消排隊了,不是應(yīng)該移除隊列嗎?
別著急,在后繼節(jié)點被喚醒后,它會在搶鎖時調(diào)用的shouldParkAfterFailedAcquire方法里面跳過已經(jīng)CANCEL的節(jié)點,那個時候,當(dāng)前節(jié)點就會被移出隊列了。
由于tryLock僅僅是用于檢查鎖在當(dāng)前調(diào)用的時候是不是可獲得的,所以即使現(xiàn)在使用的是非公平鎖,在調(diào)用這個方法時,當(dāng)前線程也會直接嘗試去獲取鎖,哪怕這個時候隊列中還有在等待中的線程。所以這一方法對于公平鎖和非公平鎖的實現(xiàn)是一樣的,它被定義在Sync類中,由FairSync和NonfairSync直接繼承使用:
public boolean tryLock() { return sync.nonfairTryAcquire(1); }
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
這個nonfairTryAcquire我們在上面分析非公平鎖的lock方法時已經(jīng)講過了,這里只是簡單的方法復(fù)用。該方法不存在任何和隊列相關(guān)的操作,僅僅就是直接嘗試去獲鎖,成功了就返回true,失敗了就返回false。
可能大家會覺得公平鎖也使用這種方式去tryLock就喪失了公平性,但是這種方式在某些情況下是非常有用的,如果你還是想維持公平性,那應(yīng)該使用帶超時機(jī)制的tryLock:
tryLock(long timeout, TimeUnit unit)與立即返回的tryLock()不同,tryLock(long timeout, TimeUnit unit)帶了超時時間,所以是阻塞式的,并且在獲取鎖的過程中可以響應(yīng)中斷異常:
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); }
與lockInterruptibly方法一樣,該方法首先檢查當(dāng)前線程是否已經(jīng)被中斷過了,如果已經(jīng)被中斷了,則立即拋出InterruptedException。
隨后我們通過調(diào)用tryAcquire和doAcquireNanos(arg, nanosTimeout)方法來嘗試獲取鎖,注意,這時公平鎖和非公平鎖對于tryAcquire方法就有不同的實現(xiàn)了,公平鎖首先會檢查當(dāng)前有沒有別的線程在隊列中排隊,關(guān)于公平鎖和非公平鎖對tryAcquire的不同實現(xiàn)上文已經(jīng)講過了,這里不再贅述。我們直接來看doAcquireNanos,這個方法其實和前面說的doAcquireInterruptibly方法很像,我們通過將相同的部分注釋掉,直接看不同的部分:
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout <= 0L) return false; final long deadline = System.nanoTime() + nanosTimeout; /*final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false;*/ return true; // doAcquireInterruptibly中為 return /*}*/ nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); /* } } finally { if (failed) cancelAcquire(node); }*/ }
可以看出,這兩個方法的邏輯大差不差,只是doAcquireNanos多了對于截止時間的檢查。
不過這里有兩點需要注意,一個是doAcquireInterruptibly是沒有返回值的,而doAcquireNanos是有返回值的。這是因為doAcquireNanos有可能因為獲取到鎖而返回,也有可能因為超時時間到了而返回,為了區(qū)分這兩種情況,因為超時時間而返回時,我們將返回false,代表并沒有獲取到鎖。
另外一點值得注意的是,上面有一個nanosTimeout > spinForTimeoutThreshold的條件,在它滿足的時候才會將當(dāng)前線程掛起指定的時間,這個spinForTimeoutThreshold是個啥呢:
/** * The number of nanoseconds for which it is faster to spin * rather than to use timed park. A rough estimate suffices * to improve responsiveness with very short timeouts. */ static final long spinForTimeoutThreshold = 1000L;
它就是個閾值,是為了提升性能用的。如果當(dāng)前剩下的等待時間已經(jīng)很短了,我們就直接使用自旋的形式等待,而不是將線程掛起,可見作者為了盡可能地優(yōu)化AQS鎖的性能費足了心思。
unlock()unlock操作用于釋放當(dāng)前線程所占用的鎖,這一點對于公平鎖和非公平鎖的實現(xiàn)是一樣的,所以該方法被定義在Sync類中,由FairSync和NonfairSync直接繼承使用:
public void unlock() { sync.release(1); }
關(guān)于ReentrantLock的釋放鎖的操作,我們在逐行分析AQS源碼(2)——獨占鎖的釋放中已經(jīng)詳細(xì)的介紹過了,這里就不再贅述了。
newCondition()ReentrantLock本身并沒有實現(xiàn)Condition方法,它是直接調(diào)用了AQS的newCondition方法
public Condition newCondition() { return sync.newCondition(); }
而AQS的newCondtion方法就是簡單地創(chuàng)建了一個ConditionObject對象:
final ConditionObject newCondition() { return new ConditionObject(); }
關(guān)于ConditionObject對象的源碼分析,請參見 逐行分析AQS源碼(4)——Condition接口實現(xiàn)
總結(jié)ReentrantLock對于Lock接口方法的實現(xiàn)大多數(shù)是直接調(diào)用了AQS的方法,AQS中已經(jīng)完成了大多數(shù)邏輯的實現(xiàn),子類只需要直接繼承使用即可,這足見AQS在并發(fā)編程中的地位。當(dāng)然,有一些邏輯還是需要ReentrantLock自己去實現(xiàn)的,例如tryAcquire的邏輯。
AQS在并發(fā)編程中的地位舉足輕重,只要弄懂了它,我們在學(xué)習(xí)其他并發(fā)編程工具的時候就會容易很多。
(完)
系列文章目錄
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/77237.html
摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續(xù)更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因為寫作的時候發(fā)現(xiàn),為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...
摘要:例如,線程需要互相等待,保證所有線程都執(zhí)行完了之后才能一起通過。獲取正在等待中的線程數(shù)注意,這里加了鎖,因為方法可能會被多個線程同時修改。只要有一行沒有處理完,所有的線程都會在處等待,最后一個執(zhí)行完的線程將會負(fù)責(zé)喚醒所有等待的線程 前言 系列文章目錄 上一篇 我們學(xué)習(xí)了基于AQS共享鎖實現(xiàn)的CountDownLatch,本篇我們來看看另一個和它比較像的并發(fā)工具CyclicBarrier...
摘要:創(chuàng)建線程的方式方式一將類聲明為的子類。將該線程標(biāo)記為守護(hù)線程或用戶線程。其中方法隱含的線程為父線程?;謴?fù)線程,已過時。等待該線程銷毀終止。更多的使當(dāng)前線程在鎖存器倒計數(shù)至零之前一直等待,除非線 知識體系圖: showImg(https://segmentfault.com/img/bVbef6v?w=1280&h=960); 1、線程是什么? 線程是進(jìn)程中獨立運行的子任務(wù)。 2、創(chuàng)建線...
摘要:所以接下來,我們需要簡單的介紹下多線程中的并發(fā)通信模型。比如中,以及各種鎖機(jī)制,均為了解決線程間公共狀態(tài)的串行訪問問題。 并發(fā)的學(xué)習(xí)門檻較高,相較單純的羅列并發(fā)編程 API 的枯燥被動學(xué)習(xí)方式,本系列文章試圖用一個簡單的栗子,一步步結(jié)合并發(fā)編程的相關(guān)知識分析舊有實現(xiàn)的不足,再實現(xiàn)邏輯進(jìn)行分析改進(jìn),試圖展示例子背后的并發(fā)工具與實現(xiàn)原理。 本文是本系列的第一篇文章,提出了一個簡單的業(yè)務(wù)場景...
閱讀 605·2023-04-25 14:26
閱讀 1381·2021-11-25 09:43
閱讀 3546·2021-09-22 15:25
閱讀 1509·2019-08-30 15:54
閱讀 615·2019-08-30 12:57
閱讀 835·2019-08-29 17:24
閱讀 3218·2019-08-28 18:13
閱讀 2780·2019-08-28 17:52