jQuery選擇器
815
2022-05-29
AQS的主要方法源碼解析
AQS的設計是基于模板方法模式的,它有一些方法必須要子類去實現(xiàn)的,它們主要有:
isHeldExclusively():該線程是否正在獨占資源。只有用到condition才需要去實現(xiàn)它。
tryAcquire(int):獨占方式。嘗試獲取資源,成功則返回true,失敗則返回false。
tryRelease(int):獨占方式。嘗試釋放資源,成功則返回true,失敗則返回false。
tryAcquireShared(int):共享方式。嘗試獲取資源。負數(shù)表示失?。?表示成功,但沒有剩余可用資源;正數(shù)表示成功,且有剩余資源。
tryReleaseShared(int):共享方式。嘗試釋放資源,如果釋放后允許喚醒后續(xù)等待結點返回true,否則返回false。
這些方法雖然都是protected方法,但是它們并沒有在AQS具體實現(xiàn),而是直接拋出異常(這里不使用抽象方法的目的是:避免強迫子類中把所有的抽象方法都實現(xiàn)一遍,減少無用功,這樣子類只需要實現(xiàn)自己關心的抽象方法即可,比如 Semaphore 只需要實現(xiàn) tryAcquire 方法而不用實現(xiàn)其余不需要用到的模版方法):
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
而AQS實現(xiàn)了一系列主要的邏輯。下面我們從源碼來分析一下獲取和釋放資源的主要邏輯:
獲取資源
獲取資源的入口是acquire(int arg)方法。arg是要獲取的資源的個數(shù),在獨占模式下始終為1。我們先來看看這個方法的邏輯:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
首先調(diào)用tryAcquire(arg)嘗試去獲取資源。前面提到了這個方法是在子類具體實現(xiàn)的。
如果獲取資源失敗,就通過addWaiter(Node.EXCLUSIVE)方法把這個線程插入到等待隊列中。其中傳入的參數(shù)代表要插入的Node是獨占式的。這個方法的具體實現(xiàn):
private Node addWaiter(Node mode) { // 生成該線程對應的Node節(jié)點 Node node = new Node(Thread.currentThread(), mode); // 將Node插入隊列中 Node pred = tail; if (pred != null) { node.prev = pred; // 使用CAS嘗試,如果成功就返回 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 如果等待隊列為空或者上述CAS失敗,再自旋CAS插入 enq(node); return node; } // 自旋CAS插入等待隊列 private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
上面的兩個函數(shù)比較好理解,就是在隊列的尾部插入新的Node節(jié)點,但是需要注意的是由于AQS中會存在多個線程同時爭奪資源的情況,因此肯定會出現(xiàn)多個線程同時插入節(jié)點的操作,在這里是通過CAS自旋的方式保證了操作的線程安全性。
OK,現(xiàn)在回到最開始的aquire(int arg)方法。現(xiàn)在通過addWaiter方法,已經(jīng)把一個Node放到等待隊列尾部了。而處于等待隊列的結點是從頭結點一個一個去獲取資源的。具體的實現(xiàn)我們來看看acquireQueued方法
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // 自旋 for (;;) { final Node p = node.predecessor(); // 如果node的前驅結點p是head,表示node是第二個結點,就可以嘗試去獲取資源了 if (p == head && tryAcquire(arg)) { // 拿到資源后,將head指向該結點。 // 所以head所指的結點,就是當前獲取到資源的那個結點或null。 setHead(node); p.next = null; // help GC failed = false; return interrupted; } // 如果自己可以休息了,就進入waiting狀態(tài),直到被unpark() if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
這里parkAndCheckInterrupt方法內(nèi)部使用到了LockSupport.park(this),順便簡單介紹一下park。
LockSupport類是Java 6 引入的一個類,提供了基本的線程同步原語。LockSupport實際上是調(diào)用了Unsafe類里的函數(shù),歸結到Unsafe里,只有兩個函數(shù):
park(boolean isAbsolute, long time):阻塞當前線程
unpark(Thread jthread):使給定的線程停止阻塞
所以結點進入等待隊列后,是調(diào)用park使它進入阻塞狀態(tài)的。只有頭結點的線程是處于活躍狀態(tài)的。
當然,獲取資源的方法除了acquire外,還有以下三個:
acquireInterruptibly:申請可中斷的資源(獨占模式)
acquireShared:申請共享模式的資源
acquireSharedInterruptibly:申請可中斷的資源(共享模式)
任務調(diào)度
版權聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權內(nèi)容。