国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)

這篇具有很好參考價值的文章主要介紹了10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)

前言

上篇文章15000字、6個代碼案例、5個原理圖讓你徹底搞懂Synchronized有說到synchronized由object monitor實現(xiàn)的

object monitor中由cxq棧和entry list來實現(xiàn)阻塞隊列,wait set實現(xiàn)等待隊列,從而實現(xiàn)synchronized的等待/通知模式

而JDK中的JUC并發(fā)包也通過類似的阻塞隊列和等待隊列實現(xiàn)等待/通知模式

這篇文章就來講講JUC的基石AQS(AbstractQueuedSynchronizer)

需要了解的前置知識:CAS、volatile

如果不了解CAS可以看上篇講述synchronized的文章(鏈接在上面)

如果不了解volatile可以看這篇文章 5個案例和流程圖讓你從0到1搞懂volatile關鍵字

本篇文章以AQS為中心,深入淺出描述AQS中的數(shù)據(jù)結構、設計以及獲取、釋放同步狀態(tài)的源碼流程、Condition等

觀看本文大約需要10分鐘,可以帶著幾個問題去觀看

  1. 什么是AQS,它是干啥用的?
  2. AQS是使用什么數(shù)據(jù)結構實現(xiàn)的?
  3. AQS獲取/釋放同步狀態(tài)是如何實現(xiàn)的?
  4. AQS除了具有synchronized的功能還擁有什么其他特性?
  5. AQS如何去實現(xiàn)非公平鎖、公平鎖?
  6. 什么是Condition?它跟AQS是什么關系?

AQS數(shù)據(jù)結構

什么是AQS呢?

AQS是一個同步隊列(阻塞隊列),是并發(fā)包中的基礎,很多并發(fā)包中的同步組件底層都使用AQS來實現(xiàn),比如:ReentrantLock、讀寫鎖、信號量等等...

AQS有三個重要的字段,分別是: head 頭節(jié)點、tail 尾節(jié)點、state 同步狀態(tài)

public abstract class AbstractQueuedSynchronizer
 ? ?extends AbstractOwnableSynchronizer
 ? ?implements java.io.Serializable {
 ? ?//頭節(jié)點
 ? ?private transient volatile Node head;
    //尾節(jié)點
 ? ?private transient volatile Node tail;
 ? ?//同步狀態(tài)
 ? ?private volatile int state; ? 
} ? ?

頭尾節(jié)點很好理解,因為AQS本身就是個雙向鏈表,那么state同步狀態(tài)是什么?

AQS中使用同步狀態(tài)表示資源,然后使用CAS來獲取/釋放資源,比如設置資源為1,一個線程來嘗試獲取資源,由于同步狀態(tài)目前為1,于是該線程CAS替換同步狀態(tài)為0,成功后表示獲取到資源,之后其他線程再來獲取資源就無法獲取了(狀態(tài)為0),直到獲取資源的線程來釋放資源

上述獲取/釋放資源也可以理解成獲取/釋放鎖

同時三個字段都被volatile修飾,用volatile來保證內存可見性,防止其他線程修改這些數(shù)據(jù)時當前線程無法感知

通過上面的描述,我們可以知道AQS大概長這樣

10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)

當某個線程獲取資源失敗時,會被構建成節(jié)點加入AQS中

節(jié)點Node是AQS中的內部類,Node中有些重要的字段一起來看看

static final class Node {
     ? ?//節(jié)點狀態(tài)
 ? ? ? ?volatile int waitStatus;
 ? ?
 ?      //前驅節(jié)點
 ? ? ? ?volatile Node prev;
?
 ? ?    //后繼節(jié)點
 ? ? ? ?volatile Node next;
        
 ?      //當前節(jié)點所代表的線程
 ? ? ? ?volatile Thread thread;
?
 ?      //等待隊列使用時的后繼節(jié)點指針
 ? ? ? ?Node nextWaiter;
}

prev、next、thread應該都好理解

AQS同步隊列和等待隊列都使用這種節(jié)點,當?shù)却犃泄?jié)點被喚醒出隊時,方便加入同步隊列

nextWaiter就是用于節(jié)點在等待隊列中指向下一個節(jié)點

waitStatus表示節(jié)點的狀態(tài)

狀態(tài) 說明
INITIAL 0 初始狀態(tài)
CANCELLED 1 該節(jié)點對應的線程取消調度
SIGNAL -1 該節(jié)點對應的線程阻塞,等待喚醒競爭資源
CONDITION -2 該節(jié)點在等待(條件)隊列中,等待喚醒后從等待隊列出隊進入同步隊列競爭
PROPAGATE -3 共享情況下,會喚醒后續(xù)所有共享節(jié)點

不太理解狀態(tài)不要緊,我們后文遇到再說

經(jīng)過上面的描述,節(jié)點大概是長成這樣的

10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)

AQS中還有另外一個內部類ConditionObject用于實現(xiàn)等待隊列/條件隊列,我們后文再來說說

AQS中可以分為獨占、共享模式,其中這兩種模式下還可以支持響應中斷、納秒級別超時

獨占模式可以理解為同一時間只有一個線程能夠獲取同步狀態(tài)

共享模式可以理解為可以有多個線程能夠獲取同步狀態(tài),方法中常用shared標識

方法中常用acquire標識獲取同步狀態(tài),release標識釋放同步狀態(tài)

10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)

這些方法都是模板方法,規(guī)定流程,將具體的實現(xiàn)留給實現(xiàn)類去做(比如獲取同步狀態(tài),該如何獲取交給實現(xiàn)類去實現(xiàn))

獨占式

獨占式實際就是時刻上只允許一個線程獨占該資源,多線程競爭情況下也只能有一個線程獲取同步狀態(tài)成功

獲取同步狀態(tài)

不響應中斷的獨占獲取和響應中斷、超時的類似,我們以acquire為例查看源碼

    public final void acquire(int arg) {
 ? ? ? ?if (!tryAcquire(arg) &&
 ? ? ? ? ? ?acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 ? ? ? ? ? ?selfInterrupt();
 ?  }

tryAcquire 方法用于嘗試獲取同步狀態(tài),參數(shù)arg表示獲取多少同步狀態(tài),獲取成功返回true 則會退出方法,留給實現(xiàn)類去實現(xiàn)

addWaiter

addWaiter(Node.EXCLUSIVE) 構建獨占式節(jié)點,并用CAS+失敗重試的方式加入AQS的末尾

    private Node addWaiter(Node mode) {
 ? ? ? ?//構建節(jié)點
 ? ? ? ?Node node = new Node(Thread.currentThread(), mode);
 ? ? ? ?//尾節(jié)點不為空則CAS替換尾節(jié)點
 ? ? ? ?Node pred = tail;
 ? ? ? ?if (pred != null) {
 ? ? ? ? ? ?node.prev = pred;
 ? ? ? ? ? ?if (compareAndSetTail(pred, node)) {
 ? ? ? ? ? ? ? ?pred.next = node;
 ? ? ? ? ? ? ? ?return node;
 ? ? ? ? ?  }
 ? ? ?  }
 ? ? ? ?//尾節(jié)點為空或則CAS失敗執(zhí)行enq
 ? ? ? ?enq(node);
 ? ? ? ?return node;
 ?  }
 ? ?private Node enq(final Node node) {
 ? ? ? ?//失敗重試
 ? ? ? ?for (;;) {
 ? ? ? ? ? ?Node t = tail;
 ? ? ? ? ? ?//沒有尾節(jié)點 則CAS設置頭節(jié)點(頭尾節(jié)點為一個節(jié)點),否則CAS設置尾節(jié)點
 ? ? ? ? ? ?if (t == null) { // Must initialize
 ? ? ? ? ? ? ? ?if (compareAndSetHead(new Node()))
 ? ? ? ? ? ? ? ? ? ?tail = head;
 ? ? ? ? ?  } else {
 ? ? ? ? ? ? ? ?node.prev = t;
 ? ? ? ? ? ? ? ?if (compareAndSetTail(t, node)) {
 ? ? ? ? ? ? ? ? ? ?t.next = node;
 ? ? ? ? ? ? ? ? ? ?return t;
 ? ? ? ? ? ? ?  }
 ? ? ? ? ?  }
 ? ? ?  }
 ?  }

enq方法主要以自旋(中途不會進入等待模式)去CAS設置尾節(jié)點,如果AQS中沒有節(jié)點則頭尾節(jié)點為同一節(jié)點

由于添加到尾節(jié)點存在競爭,因此需要用CAS去替換尾節(jié)點

10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)

acquireQueued

acquireQueued方法主要用于AQS隊列中的節(jié)點來自旋獲取同步狀態(tài),在這個自旋中并不是一直執(zhí)行的,而是會被park進入等待

final boolean acquireQueued(final Node node, int arg) {
 ? ?//記錄是否失敗
 ? ?boolean failed = true;
 ? ?try {
 ? ? ? ?//記錄是否中斷過
 ? ? ? ?boolean interrupted = false;
 ? ? ? ?//失敗重試 
 ? ? ? ?for (;;) {
 ? ? ? ? ? ?//p 前驅節(jié)點
 ? ? ? ? ? ?final Node p = node.predecessor();
 ? ? ? ? ? ?//如果前驅節(jié)點為頭節(jié)點,并嘗試獲取同步狀態(tài)成功則返回
 ? ? ? ? ? ?if (p == head && tryAcquire(arg)) {
 ? ? ? ? ? ? ? ?//設置頭節(jié)點
 ? ? ? ? ? ? ? ?setHead(node);
 ? ? ? ? ? ? ? ?p.next = null; // help GC
 ? ? ? ? ? ? ? ?failed = false;
 ? ? ? ? ? ? ? ?return interrupted;
 ? ? ? ? ?  }
 ? ? ? ? ? ?//失敗則設置下標記然后進入等待檢查中斷
 ? ? ? ? ? ?if (shouldParkAfterFailedAcquire(p, node) &&
 ? ? ? ? ? ? ? ?parkAndCheckInterrupt())
 ? ? ? ? ? ? ? ?interrupted = true;
 ? ? ?  }
 ?  } finally {
 ? ? ? ?//如果失敗則取消獲取
 ? ? ? ?if (failed)
 ? ? ? ? ? ?cancelAcquire(node);
 ?  }
}

在嘗試獲取同步狀態(tài)前有個條件p == head && tryAcquire(arg):前驅節(jié)點是頭節(jié)點

因此AQS中的節(jié)點獲取狀態(tài)是FIFO的

但即使?jié)M足前驅節(jié)點是頭節(jié)點,并不一定就能獲取同步狀態(tài)成功,因為還未加入AQS的線程也可能嘗試獲取同步狀態(tài),以此來實現(xiàn)非公平鎖

那如何實現(xiàn)公平鎖呢?

在嘗試獲取同步狀態(tài)前都加上這個條件就行了唄!

再來看看shouldParkAfterFailedAcquire 獲取同步狀態(tài)失敗后應該停放

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 ? ?//前驅節(jié)點狀態(tài)
 ? ?int ws = pred.waitStatus;
 ? ?if (ws == Node.SIGNAL)
 ? ? ? ?//前驅節(jié)點狀態(tài)是SIGNAL 說明前驅釋放同步狀態(tài)回來喚醒 直接返回
 ? ? ? ?return true;
 ? ?if (ws > 0) {
 ? ? ? ?//如果前驅狀態(tài)大于0 說明被取消了,就一直往前找,找到?jīng)]被取消的節(jié)點
 ? ? ? ?do {
 ? ? ? ? ? ?node.prev = pred = pred.prev;
 ? ? ?  } while (pred.waitStatus > 0);
 ? ? ? ?//排在沒被取消的節(jié)點后面
 ? ? ? ?pred.next = node;
 ?  } else {
 ? ? ? ?//前驅沒被取消,而且狀態(tài)不是SIGNAL CAS將狀態(tài)更新為SIGNAL,釋放同步狀態(tài)要來喚醒
 ? ? ? ?compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
 ?  }
 ? ?return false;
}

實際上是park前的一些準備

再來看看 parkAndCheckInterrupt ,用工具類進入等待狀態(tài),被喚醒后檢查是否中斷

private final boolean parkAndCheckInterrupt() {
 ? ? ? ?//線程進入等待狀態(tài)... 
 ? ? ? ?LockSupport.park(this);
 ? ? ? ? //檢查是否中斷 (會清除中斷標記位)
 ? ? ? ?return Thread.interrupted();
}

acquireQueued的中如果未獲取同步狀態(tài)并且拋出異常,最終會執(zhí)行cancelAcquire取消

當感知到中斷時返回true回去,來到第一層acquire方法執(zhí)行selfInterrupt方法,自己中斷線程

acquire流程圖:

  1. 先嘗試獲取同步狀態(tài)失敗則CAS+失敗重試添加到AQS末尾
  1. 前驅節(jié)點為頭節(jié)點且獲取同步狀態(tài)成功則返回,否則進入等待狀態(tài)等待喚醒,喚醒后重試
  1. 在2期間發(fā)生異常取消當前節(jié)點

10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)

釋放同步狀態(tài)

先進行釋放同步狀態(tài),成功后頭節(jié)點狀態(tài)不為0 喚醒下一個狀態(tài)不是被取消的節(jié)點

public final boolean release(int arg) {
 ? ?//釋放同步狀態(tài)
 ? ?if (tryRelease(arg)) {
 ? ? ? ?Node h = head;
 ? ? ? ?if (h != null && h.waitStatus != 0)
 ? ? ? ? ? ?//喚醒下一個狀態(tài)不大于0(大于0就是取消)的節(jié)點
 ? ? ? ? ? ?unparkSuccessor(h);
 ? ? ? ?return true;
 ?  }
 ? ?return false;
}

響應中斷

acquireInterruptibly用于響應中斷的獲取同步狀態(tài)

public final void acquireInterruptibly(int arg)
 ? ? ? ?throws InterruptedException {
 ? ?//查看是否被中斷,中斷拋出異常
 ? ?if (Thread.interrupted())
 ? ? ? ?throw new InterruptedException();
 ? ?if (!tryAcquire(arg))
 ? ? ? ?doAcquireInterruptibly(arg);
}

doAcquireInterruptibly 與原過程類似,就是在被喚醒后檢查到被中斷時拋出中斷異常

    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;
 ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ? ?if (shouldParkAfterFailedAcquire(p, node) &&
 ? ? ? ? ? ? ? ? ? ?parkAndCheckInterrupt())
 ? ? ? ? ? ? ? ? ? ?//被喚醒后檢查到被中斷時拋出中斷異常
 ? ? ? ? ? ? ? ? ? ?throw new InterruptedException();
 ? ? ? ? ?  }
 ? ? ?  } finally {
 ? ? ? ? ? ?if (failed)
 ? ? ? ? ? ? ? ?cancelAcquire(node);
 ? ? ?  }
 ?  }

響應中斷的獲取同步狀態(tài)被中斷時會直接拋出中斷異常,而不響應的是自己中斷

響應超時

響應超時的獲取同步狀態(tài)使用tryAcquireNanos 超時時間為納秒級別

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
 ? ? ? ?throws InterruptedException {
 ? ?if (Thread.interrupted())
 ? ? ? ?throw new InterruptedException();
 ? ?return tryAcquire(arg) ||
 ? ? ? ?doAcquireNanos(arg, nanosTimeout);
}

可以看出響應超時同時也會響應中斷

doAcquireNanos也與原過程類似

    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;
 ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ? ?//還有多久超時
 ? ? ? ? ? ? ? ?nanosTimeout = deadline - System.nanoTime();
 ? ? ? ? ? ? ? ?if (nanosTimeout <= 0L)
 ? ? ? ? ? ? ? ? ? ?//已超時
 ? ? ? ? ? ? ? ? ? ?return false;
 ? ? ? ? ? ? ? ?if (shouldParkAfterFailedAcquire(p, node) &&
 ? ? ? ? ? ? ? ? ? ?//大于1ms
 ? ? ? ? ? ? ? ? ? ?nanosTimeout > spinForTimeoutThreshold)
 ? ? ? ? ? ? ? ? ? ?//超時等待
 ? ? ? ? ? ? ? ? ? ?LockSupport.parkNanos(this, nanosTimeout);
 ? ? ? ? ? ? ? ?//響應中斷
 ? ? ? ? ? ? ? ?if (Thread.interrupted())
 ? ? ? ? ? ? ? ? ? ?throw new InterruptedException();
 ? ? ? ? ?  }
 ? ? ?  } finally {
 ? ? ? ? ? ?if (failed)
 ? ? ? ? ? ? ? ?cancelAcquire(node);
 ? ? ?  }
 ?  }

響應超時在自旋期間會計算還有多久超時,如果大于1ms就等待對應的時間,否則就繼續(xù)自旋,同時響應中斷

共享

共享式就是允許多個線程同時獲取一定的資源,比如信號量、讀鎖就是用共享式實現(xiàn)的

其實共享式與獨占式流程類似,只是嘗試獲取同步狀態(tài)的實現(xiàn)不同

我們用個獲取同步狀態(tài)的方法來說明

共享式獲取同步狀態(tài)使用acquireShared

public final void acquireShared(int arg) {
 ? ?if (tryAcquireShared(arg) < 0)
 ? ? ? ?doAcquireShared(arg);
}

tryAcquireShared 嘗試獲取同步狀態(tài),參數(shù)arg表示獲取多少同步狀態(tài),返回剩余可獲取同步狀態(tài)的數(shù)量

如果剩余可獲取同步狀態(tài)數(shù)量小于0 說明 未獲取成功進入doAcquireShared

    private void doAcquireShared(int arg) {
 ? ? ? ?//添加共享式節(jié)點
 ? ? ? ?final Node node = addWaiter(Node.SHARED);
 ? ? ? ?boolean failed = true;
 ? ? ? ?try {
 ? ? ? ? ? ?boolean interrupted = false;
 ? ? ? ? ? ?for (;;) {
 ? ? ? ? ? ? ? ?//獲取前驅節(jié)點
 ? ? ? ? ? ? ? ?final Node p = node.predecessor();
 ? ? ? ? ? ? ? ?if (p == head) {
 ? ? ? ? ? ? ? ? ? ?int r = tryAcquireShared(arg);
 ? ? ? ? ? ? ? ? ? ?if (r >= 0) {
 ? ? ? ? ? ? ? ? ? ? ? ?//如果前驅節(jié)點為頭節(jié)點 并且 獲取同步狀態(tài)成功 設置頭節(jié)點
 ? ? ? ? ? ? ? ? ? ? ? ?setHeadAndPropagate(node, r);
 ? ? ? ? ? ? ? ? ? ? ? ?p.next = null; // help GC
 ? ? ? ? ? ? ? ? ? ? ? ?if (interrupted)
 ? ? ? ? ? ? ? ? ? ? ? ? ? ?selfInterrupt();
 ? ? ? ? ? ? ? ? ? ? ? ?failed = false;
 ? ? ? ? ? ? ? ? ? ? ? ?return;
 ? ? ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ?  }
 ? ? ? ? ? ? ? ?//獲取失敗進入會等待的自旋
 ? ? ? ? ? ? ? ?if (shouldParkAfterFailedAcquire(p, node) &&
 ? ? ? ? ? ? ? ? ? ?parkAndCheckInterrupt())
 ? ? ? ? ? ? ? ? ? ?interrupted = true;
 ? ? ? ? ?  }
 ? ? ?  } finally {
 ? ? ? ? ? ?if (failed)
 ? ? ? ? ? ? ? ?cancelAcquire(node);
 ? ? ?  }
 ?  }

響應中斷、超時等方法也與獨占式類似,只是有些設置細節(jié)不同

Condition

上文曾說過AQS充當阻塞(同步)隊列,Condition來充當?shù)却犃?/p>

AQS的內部類ConditionObject就是Condition的實現(xiàn),它充當?shù)却犃?,用字段記錄頭尾節(jié)點

public class ConditionObject implements Condition{
 ? ? ? ?//頭節(jié)點
 ? ?    private transient Node firstWaiter;
 ? ? ? ?//尾節(jié)點
 ? ? ? ?private transient Node lastWaiter; ?
}

節(jié)點之間使用nextWait指向下一個節(jié)點,形成單向鏈表

10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)

同時提供await系列方法來讓當前線程進入等待,signal系列方法來喚醒

        public final void await() throws InterruptedException {
 ? ? ? ? ? ?//響應中斷
 ? ? ? ? ? ?if (Thread.interrupted())
 ? ? ? ? ? ? ? ?throw new InterruptedException();
 ? ? ? ? ? ?//添加到末尾 不需要保證原子性,因為能指向await一定是獲取到同步資源的
 ? ? ? ? ? ?Node node = addConditionWaiter();
 ? ? ? ? ? ?//釋放獲取的同步狀態(tài)
 ? ? ? ? ? ?int savedState = fullyRelease(node);
 ? ? ? ? ? ?int interruptMode = 0;
 ? ? ? ? ? ?//不在同步隊列就park進入等待
 ? ? ? ? ? ?while (!isOnSyncQueue(node)) {
 ? ? ? ? ? ? ? ?LockSupport.park(this);
 ? ? ? ? ? ? ? ?if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
 ? ? ? ? ? ? ? ? ? ?break;
 ? ? ? ? ?  }
 ? ? ? ? ? ?//被喚醒后自旋獲取同步狀態(tài)
 ? ? ? ? ? ?if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
 ? ? ? ? ? ? ? ?interruptMode = REINTERRUPT;
 ? ? ? ? ? ?//取消后清理
 ? ? ? ? ? ?if (node.nextWaiter != null) // clean up if cancelled
 ? ? ? ? ? ? ? ?unlinkCancelledWaiters();
 ? ? ? ? ? ?if (interruptMode != 0)
 ? ? ? ? ? ? ? ?reportInterruptAfterWait(interruptMode);
 ? ? ?  }

await主要將節(jié)點添加到condition object末尾,釋放獲取的同步狀態(tài),進入等待,喚醒后自旋獲取同步狀態(tài)

signal的主要邏輯在transferForSignal中

    final boolean transferForSignal(Node node) {
 ? ? ? ?//CAS修改節(jié)點狀態(tài) 失敗返回 變成取消
 ? ? ? ?if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
 ? ? ? ? ? ?return false;
 ? ? ? ?//加入AQS末尾
 ? ? ? ?Node p = enq(node);
 ? ? ? ?int ws = p.waitStatus;
 ? ? ? ?//CAS將節(jié)點狀態(tài)修改為SIGNAL 成功則喚醒節(jié)點
 ? ? ? ?if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
 ? ? ? ? ? ?LockSupport.unpark(node.thread);
 ? ? ? ?return true;
 ?  }

signal 主要把狀態(tài)從-2condition 修改為 0(失敗則取消節(jié)點), 然后加入AQS的末尾,最后再將狀態(tài)該為-1 signal,成功則喚醒節(jié)點

為什么加入AQS末尾還是使用enq去CAS+失敗重試操作保證原子性呢?

因為ConditionObject允許有多個,也就一個AQS同步隊列可能對應多個Condition等待(條件)隊列

10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)

總結

本篇文章以AQS為核心,深入淺出的描述AQS實現(xiàn)的數(shù)據(jù)結構、設計思想、獲取/釋放同步資源的源碼級流程、Condition等

AQS使用頭尾節(jié)點來實現(xiàn)雙向隊列,提供同步狀態(tài)和獲取/釋放同步狀態(tài)的模板方法來實現(xiàn)阻塞(同步)隊列,并且這些字段使用volatile修飾,保證可見性與讀取的場景配合,不需要保證原子性,在寫的場景下常用CAS保證原子性

AQS與Condition使用相同類型的節(jié)點,在AQS中節(jié)點維護成雙向鏈表,在Condition中節(jié)點維護成單向鏈表,節(jié)點除了維護指向關系,還需要記錄對應線程和節(jié)點狀態(tài)

AQS分為獨占式和共享式,使用獨占式時只允許一個線程獲取同步狀態(tài),使用共享式時則允許多個線程獲取同步狀態(tài);其中還提供響應中斷、等待超時的類似方法

獲取同步狀態(tài):先嘗試獲取同步狀態(tài),如果失敗則CAS+失敗重試的方式將節(jié)點添加到AQS末尾,等待被前驅節(jié)點喚醒;只有當前驅節(jié)點為頭節(jié)點并且獲取同步狀態(tài)成功才返回,否則進入等待,被喚醒后繼續(xù)嘗試(自旋);在此期間如果發(fā)生異常,在拋出異常前會取消該節(jié)點

釋放同步狀態(tài):嘗試釋放同步狀態(tài),成功后喚醒后繼未被取消的節(jié)點

在獲取同步狀態(tài)時,被喚醒后會檢查中斷標識,如果是響應中斷的則會直接拋出中斷異常,不響應的則是在最外層自己中斷

響應超時時,在自旋獲取同步狀態(tài)期間會計時,如果距離超時小于1ms就不進入等待的自旋,大于則再等待對應時間

AQS充當阻塞隊列,Condition充當它的等待隊列來實現(xiàn)等待/通知模式,AQS的內部類ConditionObject在await時會加入Condition末尾并釋放同步狀態(tài)進入等待隊列,在被喚醒后自旋(失敗會進入等待)獲取同步狀態(tài);在single時會CAS的將condition頭節(jié)點并加入AQS尾部再去喚醒(因為一個AQS可能對應多個Condition因此要CAS保證原子性)

最后(不要白嫖,一鍵三連求求拉~)

本篇文章被收入專欄 由點到線,由線到面,深入淺出構建Java并發(fā)編程知識體系,感興趣的同學可以持續(xù)關注喔

本篇文章筆記以及案例被收入 gitee-StudyJava、 github-StudyJava 感興趣的同學可以stat下持續(xù)關注喔~

有什么問題可以在評論區(qū)交流,如果覺得菜菜寫的不錯,可以點贊、關注、收藏支持一下~

關注菜菜,分享更多干貨,公眾號:菜菜的后端私房菜

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!文章來源地址http://www.zghlxwxcb.cn/news/detail-692476.html

到了這里,關于10分鐘從源碼級別搞懂AQS(AbstractQueuedSynchronizer)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 10分鐘的時間,帶你徹底搞懂JavaScript數(shù)據(jù)類型轉換

    10分鐘的時間,帶你徹底搞懂JavaScript數(shù)據(jù)類型轉換

    前言 ??? 大家好,我是南木元元,熱衷分享有趣實用的文章,希望大家多多支持,一起進步! ???? 個人主頁: 南木元元 目錄 JS數(shù)據(jù)類型 3種轉換類型 ToBoolean ToString ToNumber 對象轉原始類型 隱式類型轉換 結語 JS數(shù)據(jù)類型 首先我們需要知道,js中數(shù)據(jù)類型分為兩大類: 基本

    2024年02月05日
    瀏覽(25)
  • 10分鐘搞懂商業(yè)模式畫布:9張分析圖、6張模板

    10分鐘搞懂商業(yè)模式畫布:9張分析圖、6張模板

    新入職、新行業(yè),新人如何快速搞懂它的業(yè)務?新領域、新業(yè)務,投資人如何快速搞明白一個公司?新商機、新模式,創(chuàng)業(yè)者如何快速一個業(yè)務的商業(yè)前景? 推薦大家使用商業(yè)模式畫布,它可以讓你輕松看透商業(yè)模式。對新入職員工、想快速了解一個領域的投資者、了解一個

    2024年02月04日
    瀏覽(34)
  • 一文讓你徹底搞懂AQS(通俗易懂的AQS)

    一文讓你徹底搞懂AQS(通俗易懂的AQS)

    AQS是一個用來構建鎖和同步器的框架,使用AQS能簡單且高效地構造出應用廣泛的大量的同步器,比如我們提到的ReentrantLock,Semaphore,其他的諸如ReentrantReadWriteLock,SynchronousQueue,F(xiàn)utureTask等等皆是基于AQS的。當然,我們自己也能利用AQS非常輕松容易地構造出符合我們自己需求的

    2024年02月17日
    瀏覽(24)
  • 基于AbstractQueuedSynchronizer之Condition源碼分析

    java.util.concurrent.locks.Condition 是一個將 Object 的監(jiān)視器方法(wait、notify 和 notifyAll)分離到不同的對象中的類,以實現(xiàn)每個對象有多個等待集合的效果,這些對象與任意 Lock 實現(xiàn)結合使用。當一個 Lock 替換了synchronized方法和語句的使用時,Condition 替換了 Object 監(jiān)視器方法的使用

    2024年02月09日
    瀏覽(24)
  • 萬字長文解析AQS抽象同步器核心原理(深入閱讀AQS源碼)

    萬字長文解析AQS抽象同步器核心原理(深入閱讀AQS源碼)

    在爭用激烈的場景下使用基于CAS自旋實現(xiàn)的輕量級鎖有兩個大的問題: CAS惡性空自旋會浪費大量的CPU資源。 在SMP架構的CPU上會導致“總線風暴”。 解決CAS惡性空自旋的有效方式之一是以空間換時間,較為常見的方案有兩種:分散操作熱點、使用隊列削峰。 JUC并發(fā)包使用的是

    2024年02月11日
    瀏覽(20)
  • 并發(fā)編程 - AQS 源碼

    1. AQS 源碼 2.?AQS 框架具體實現(xiàn) - 獨占鎖實現(xiàn) ReentrantLock 源碼 實現(xiàn) ReentrantLock 的三大核心原理: LocksSuport :加鎖解鎖 自旋 :如果沒有加鎖成功就一直自旋 CAS :保證只能有一個線程可以加鎖成功 queue 隊列:用容器保存上面未加鎖成功的阻塞線程,要解鎖的時候從容器中拿出

    2023年04月21日
    瀏覽(20)
  • 美團面試官:可重復讀隔離級別實現(xiàn)原理是什么?(一文搞懂MVCC機制)

    美團面試官:可重復讀隔離級別實現(xiàn)原理是什么?(一文搞懂MVCC機制)

    本文首發(fā)于公眾號【看點代碼再上班】,歡迎圍觀,第一時間獲取最新文章。 原文:美團面試官:可重復讀隔離級別實現(xiàn)原理是什么?(一文搞懂MVCC機制) “全文共計4270字,預計閱讀時間6分鐘 大家好,我是 tin ,這是我的第26篇原創(chuàng)文章 還記得MySQL數(shù)據(jù)庫事務都有哪些隔離

    2024年02月13日
    瀏覽(22)
  • Java多線程/AQS源碼

    持鎖模式-資源占用模式: ? 你是想要群體面試,還是單獨面試啊 ? 獨占和共享 獲鎖方式: ? 是不是要把手機關機啊,趕不趕時間是不是必須要在某個時間內面試完,是否公平啊 ? 不響應線程中斷;響應線程中斷;定時獲取鎖;公平獲??;非公平獲取 同步狀態(tài)-鎖狀態(tài);

    2024年02月15日
    瀏覽(16)
  • 一分鐘搞懂ResNet

    ResNet的輸入和輸出通常都是圖像或者圖像特征,具體輸入和輸出的尺寸和通道數(shù)取決于具體的網(wǎng)絡結構和任務。在ResNet中,輸入圖像首先經(jīng)過一個卷積層和池化層,然后通過多個殘差模塊,最后通過全局平均池化和全連接層輸出最終的分類結果。 ResNet在圖像分類、目標檢測、

    2024年02月05日
    瀏覽(20)
  • AQS源碼分析——以ReentrantLock為例

    AQS源碼分析——以ReentrantLock為例

    AQS自身屬性: Node屬性: 1. 以ReentrantLock為例,其他類舉一反三,方法lock() 2. Lock接口實現(xiàn)類,基本都是通過【聚合】了一個 【隊列同步器】的子類完成線程訪問控制的Sync實現(xiàn)lock;Sync繼承AQS;Sync又有兩個實現(xiàn)類。 ?公平鎖與非公平鎖的實現(xiàn): 接下來以非公平鎖為突破口:

    2024年02月10日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包