AQS——AbstractQueuedSynchronizer 同步框架(基于JDK14)

AQS——AbstractQueuedSynchronizer 同步框架(基于JDK14)

1、結(jié)構(gòu)說明

AbstractQueuedSynchronizer 繼承 AbstractOwnableSynchronizer,含有五個(gè)內(nèi)部類,其中比較重要的是Node,ConditionNode及ConditionObject。下面是具體類圖(由于AbstractQueuedSynchronizer 方法太多,此處已屏蔽大多數(shù)方法):

AQS類圖.png

AbstractOwnableSynchronizer:

AbstractQueuedSynchronizer 的父類,主要是設(shè)置當(dāng)前獨(dú)占鎖的線程:

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {

    /** Use serial ID even though all fields transient. */
    private static final long serialVersionUID = 3737899427754241961L;

    /**
     * Empty constructor for use by subclasses.
     */
    protected AbstractOwnableSynchronizer() { }

    /**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * Sets the thread that currently owns exclusive access.
     * A {@code null} argument indicates that no thread owns access.
     * This method does not otherwise impose any synchronization or
     * {@code volatile} field accesses.
     * @param thread the owner thread
     * 設(shè)置獨(dú)占鎖
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    /**
     * Returns the thread last set by {@code setExclusiveOwnerThread},
     * or {@code null} if never set.  This method does not otherwise
     * impose any synchronization or {@code volatile} field accesses.
     * @return the owner thread
     * 獲取獨(dú)占鎖
     */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

Node:節(jié)點(diǎn)類,AQS隊(duì)列的節(jié)點(diǎn),包含prev,next,由此可以看出,AQS隊(duì)列是個(gè)基于鏈表實(shí)現(xiàn)的雙向隊(duì)列。waiter為在該節(jié)點(diǎn)的線程,既等待獲取鎖的線程。status用來標(biāo)識(shí)當(dāng)前節(jié)點(diǎn)線程的狀態(tài),有以下值:

表示節(jié)點(diǎn)的狀態(tài)。其中包含的狀態(tài)有(此處有待):

  1. CANCELLED,值小于0,表示當(dāng)前的線程被取消;
  2. COND,值為 2,表示當(dāng)前節(jié)點(diǎn)在等待condition,也就是在condition隊(duì)列中(此時(shí)Node的實(shí)際值是3);
  3. WAITING,值為1,表示當(dāng)前節(jié)點(diǎn)在sync隊(duì)列中,等待著獲取鎖;

ExclusiveNode:獨(dú)占鎖節(jié)點(diǎn),繼承Node,無擴(kuò)展;

SharedNode:共享鎖節(jié)點(diǎn),繼承Node,無擴(kuò)展;

ConditionNode:繼承Node,與Node相比,多了nextWaiter屬性(),為Condition隊(duì)列的節(jié)點(diǎn);

// Node status bits, also used as argument and return values
static final int WAITING   = 1;          // must be 1
static final int CANCELLED = 0x80000000; // must be negative
static final int COND      = 2;          // in a condition wait

static final class ConditionNode extends Node
 implements ForkJoinPool.ManagedBlocker {
 ConditionNode nextWaiter;            // link to next waiting node

 /**
 * Allows Conditions to be used in ForkJoinPools without
 * risking fixed pool exhaustion. This is usable only for
 * untimed Condition waits, not timed versions.
 */
 public final boolean isReleasable() {
 return status <= 1 || Thread.currentThread().isInterrupted();
 }

 public final boolean block() {
 while (!isReleasable()) LockSupport.park();
 return true;
 }
}

ConditionObject:實(shí)現(xiàn)了Condition的接口,提供操作Condition隊(duì)列方法,主要包含Condition類型的firstWaiter及l(fā)astWaiter屬性(隊(duì)列頭及隊(duì)列尾);這個(gè)類是為了讓子類支持獨(dú)占模式的。await()、sign()方法就是讓線程阻塞、加入隊(duì)列、喚醒線程。AQS框架下基本各種獨(dú)占的加鎖,解鎖等操作到最后都是基于這個(gè)類實(shí)現(xiàn)的。

該類是提供給子類去使用的,在Reentrantlock有相關(guān)的使用。有人可能覺得為什么實(shí)現(xiàn)這個(gè)內(nèi)部類,又不用,而是給子類去用,那為什么不放到子類去呢?其實(shí)答案,很簡(jiǎn)單,抽象加模板模式

public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    /** First node of condition queue. */
    private transient ConditionNode firstWaiter;
    /** Last node of condition queue. */
    private transient ConditionNode lastWaiter;
    ...
 }

從以上的結(jié)構(gòu)可以看出,AQS中包含兩個(gè)隊(duì)列,一個(gè)是由Node組成的雙向隊(duì)列,也就是同步隊(duì)列,另一個(gè)則是由ConditionNode組成的單向隊(duì)列,即Condition隊(duì)列。Condition隊(duì)列由ConditionObject操作,ConditionObject實(shí)現(xiàn)Condition接口。

兩個(gè)隊(duì)列的關(guān)系如下圖所示(此圖來自網(wǎng)絡(luò)):

同步隊(duì)列及Condition隊(duì)列.png

2、同步隊(duì)列

AQS是一個(gè)基礎(chǔ)的同步框架,只是定義了部分基礎(chǔ)的方法,具體實(shí)現(xiàn)部分交由子類去實(shí)現(xiàn),以便擴(kuò)展不同的功能。

獲取鎖的流程如下:


aqs.png

可以看出來,在線程未暫停之前,每進(jìn)行一次循環(huán),都會(huì)調(diào)用tryAcquire/tryAcquireShared方法。

加入同步隊(duì)列(同時(shí),離隊(duì)也在該方法中實(shí)現(xiàn)):

//子類進(jìn)入同步隊(duì)列時(shí),調(diào)用此方法
public final void acquire(int arg) {
    //tryAcquire方法交由子類去實(shí)現(xiàn)
    if (!tryAcquire(arg))
        acquire(null, arg, false, false, false, 0L);
}


final int acquire(Node node, int arg, boolean shared,
                  boolean interruptible, boolean timed, long time) {
    Thread current = Thread.currentThread();
    byte spins = 0, postSpins = 0;   // retries upon unpark of first thread
    //first表示當(dāng)前node是否是head的下一個(gè)節(jié)點(diǎn),在出隊(duì)時(shí)需要
    boolean interrupted = false, first = false;
    Node pred = null;                // predecessor of node when enqueued

    /*
     * Repeatedly:
     *  Check if node now first
     *    if so, ensure head stable, else ensure valid predecessor
     *  if node is first or not yet enqueued, try acquiring
     *  else if node not yet created, create it
     *  else if not yet enqueued, try once to enqueue
     *  else if woken from park, retry (up to postSpins times)
     *  else if WAITING status not set, set and retry
     *  else park and clear WAITING status, and check cancellation
     */
    //第一次調(diào)用時(shí),此時(shí)的node必定為null(conditionNode獲取鎖時(shí),此時(shí)node不為null)
    //當(dāng)初始化node之后,node的prev、next等都是null
    for (;;) {
        //first == false 且 node不為null,node的前置節(jié)點(diǎn)不為null,node的前置節(jié)點(diǎn)不是head節(jié)點(diǎn)
        if (!first && (pred = (node == null) ? null : node.prev) != null &&
            !(first = (head == pred))) {
            //node 的status < 0
            if (pred.status < 0) {
                //清除已取消的節(jié)點(diǎn)
                //這個(gè)方法暫時(shí)沒看懂
                cleanQueue();           // predecessor cancelled
                continue;
            } else if (pred.prev == null) {
                //進(jìn)入此步的情景:同步隊(duì)列只有一個(gè)節(jié)點(diǎn)(即head的prev才會(huì)等于null),但是if條件中
                //限制了這種情景
                //這步?jīng)]看懂
                Thread.onSpinWait();    // ensure serialization
                continue;
            }
        }
        if (first || pred == null) {
            boolean acquired;
            try {
                //此處,調(diào)用子類的 tryAcquire/tryAcquireShared 的實(shí)現(xiàn)。如果成功,則獲取鎖,不進(jìn)入同步隊(duì)列(cas修改status變量)
                //相比JDK1.8,此處是一個(gè)小優(yōu)化,當(dāng)線程在進(jìn)入同步隊(duì)列期間,線程還沒暫停之前,在執(zhí)行方法進(jìn)入同步方法時(shí),不時(shí)直接去獲取鎖,
                //在并發(fā)較低的場(chǎng)景下,省去了進(jìn)入隊(duì)列、暫停線程的操作;但是在并發(fā)較高的情況下,tryAcquire基本是失敗的,多了tryAcquire的消耗。
                //線程被喚醒之后,也會(huì)通過此處獲取鎖,走出循環(huán)
                //共享鎖
                if (shared)
                    acquired = (tryAcquireShared(arg) >= 0);
                else
                    //獨(dú)占鎖
                    acquired = tryAcquire(arg);
            } catch (Throwable ex) {
                cancelAcquire(node, interrupted, false);
                throw ex;
            }
            //獲取鎖成功
            if (acquired) {
                if (first) {
                    //離隊(duì)
                    node.prev = null;
                    head = node;
                    pred.next = null;
                    node.waiter = null;
                    if (shared)
                        signalNextIfShared(node);
                    if (interrupted)
                        current.interrupt();
                }
                return 1;
            }
        }
        //根據(jù)是否是獨(dú)占鎖,創(chuàng)建對(duì)應(yīng)的節(jié)點(diǎn)
        if (node == null) {                 // allocate; retry before enqueue
            if (shared)
                node = new SharedNode();
            else
                node = new ExclusiveNode();
        } else if (pred == null) {          // try to enqueue
            //初始化節(jié)點(diǎn)
            node.waiter = current;
            Node t = tail;
            //尾節(jié)點(diǎn)
            node.setPrevRelaxed(t);         // avoid unnecessary fence
            if (t == null)
                //初始化頭尾節(jié)點(diǎn)(head == tail)
                tryInitializeHead();
            //cas修改尾節(jié)點(diǎn)和新節(jié)點(diǎn)(即移動(dòng)tail節(jié)點(diǎn)的位置)
            //此處是節(jié)點(diǎn)入隊(duì)列的關(guān)鍵操作
            else if (!casTail(t, node))
                node.setPrevRelaxed(null);  // back out
            else
                t.next = node;
        } else if (first && spins != 0) {
            //非公平鎖時(shí)會(huì)進(jìn)入該步驟
            //即線程被喚醒,卻沒有獲取到鎖
            --spins;                        // reduce unfairness on rewaits
            //jdk9新方法,優(yōu)化自旋 改善自旋等待循環(huán)中的響應(yīng)時(shí)間
            Thread.onSpinWait();
        } else if (node.status == 0) {
            //初始化status
            node.status = WAITING;          // enable signal and recheck
        } else {
            long nanos;
            spins = postSpins = (byte)((postSpins << 1) | 1);
            if (!timed)
                //暫停線程
                LockSupport.park(this);
            else if ((nanos = time - System.nanoTime()) > 0L)
                LockSupport.parkNanos(this, nanos);
            else
                break;
            //被喚醒,此時(shí)清除狀態(tài)
            node.clearStatus();
            if ((interrupted |= Thread.interrupted()) && interruptible)
                break;
        }
    }
    return cancelAcquire(node, interrupted, interruptible);
}

喚醒線程,通知線程離隊(duì):

public final boolean release(int arg) {
    //調(diào)用子類方法
    if (tryRelease(arg)) {
        //喚醒下一個(gè)節(jié)點(diǎn)
        signalNext(head);
        return true;
    }
    return false;
}

private static void signalNext(Node h) {
    Node s;
    if (h != null && (s = h.next) != null && s.status != 0) {
        //設(shè)置等待狀態(tài)(此時(shí)該節(jié)點(diǎn)的線程將要被喚醒)
        s.getAndUnsetStatus(WAITING);
        //喚醒線程
        LockSupport.unpark(s.waiter);
    }
}

3、Condition隊(duì)列

Condition隊(duì)列是獨(dú)立與同步隊(duì)列的隊(duì)列,一個(gè)同步隊(duì)列可以對(duì)應(yīng)多個(gè)Condition隊(duì)列。

入隊(duì):

/**
* Implements interruptible condition wait.
* <ol>
* <li>If current thread is interrupted, throw InterruptedException.
* <li>Save lock state returned by {@link #getState}.
* <li>Invoke {@link #release} with saved state as argument,
*     throwing IllegalMonitorStateException if it fails.
* <li>Block until signalled or interrupted.
* <li>Reacquire by invoking specialized version of
*     {@link #acquire} with saved state as argument.
* <li>If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //構(gòu)造ConditionNode
    ConditionNode node = new ConditionNode();
    //填充conditionNode,并且condition隊(duì)列入隊(duì),同步隊(duì)列離隊(duì)
    int savedState = enableWait(node);
    //暫存線程 jdk14新方法
    LockSupport.setCurrentBlocker(this); // for back-compatibility
    boolean interrupted = false, cancelled = false;
    //當(dāng)node不在condition隊(duì)列時(shí),當(dāng)線程被喚醒時(shí),node已不在condition隊(duì)列,而是在同步隊(duì)列
    while (!canReacquire(node)) {
        if (interrupted |= Thread.interrupted()) {
            //如果線程被中斷并且當(dāng)前狀態(tài)值為3
            //COND 按位非得 mask = -(COND+1),status = status & mask,
            //mask == -3,以status的取值,只有當(dāng)status == 3時(shí),才退出
            if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0)
                break;              // else interrupted after signal
        } else if ((node.status & COND) != 0) {
            try {
                //掛起線程,調(diào)用 ConditionNode 的block方法
                // 喚醒條件:
                // status <= 1 || Thread.currentThread().isInterrupted()
                ForkJoinPool.managedBlock(node);
            } catch (InterruptedException ie) {
                interrupted = true;
            }
        } else
            Thread.onSpinWait();    // awoke while enqueuing
    }
    LockSupport.setCurrentBlocker(null);
    node.clearStatus();
    //重新競(jìng)爭(zhēng)鎖
    acquire(node, savedState, false, false, false, 0L);
    if (interrupted) {
        if (cancelled) {
            unlinkCancelledWaiters(node);
            throw new InterruptedException();
        }
        Thread.currentThread().interrupt();
    }
}


/**
 * Adds node to condition list and releases lock.
 *
 * @param node the node
 * @return savedState to reacquire after wait
 */
private int enableWait(ConditionNode node) {
    //是否持有獨(dú)占鎖
    if (isHeldExclusively()) {
        //設(shè)置node的線程
        node.waiter = Thread.currentThread();
        //coonditionNode的status 為2|1 = 3
        node.setStatusRelaxed(COND | WAITING);
        ConditionNode last = lastWaiter;
        //進(jìn)入condition隊(duì)列
        if (last == null)
            firstWaiter = node;
        else
            last.nextWaiter = node;
        lastWaiter = node;
        //獲取status
        int savedState = getState();
        //釋放鎖,注意,進(jìn)入condition隊(duì)列將釋放鎖,且離開同步隊(duì)列
        if (release(savedState))
            return savedState;
    }
    node.status = CANCELLED; // lock not held or inconsistent
    throw new IllegalMonitorStateException();
}

出隊(duì):

/**
 * Moves the longest-waiting thread, if one exists, from the
 * wait queue for this condition to the wait queue for the
 * owning lock.
 *
 * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
 *         returns {@code false}
 */
public final void signal() {
    ConditionNode first = firstWaiter;
    //如果當(dāng)前線程沒有獨(dú)占鎖的話,拋異常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    if (first != null)
        //condition隊(duì)列還有節(jié)點(diǎn)
        doSignal(first, false);
}

/**
 * Removes and transfers one or all waiters to sync queue.
 */
private void doSignal(ConditionNode first, boolean all) {
    while (first != null) {
        ConditionNode next = first.nextWaiter;
        if ((firstWaiter = next) == null)
            lastWaiter = null;
        //此處判斷status是否等于3,即是否處于等待中
        if ((first.getAndUnsetStatus(COND) & COND) != 0) {
            //重新進(jìn)入同步隊(duì)列隊(duì)尾
            enqueue(first);
            if (!all)
                break;
        }
        first = next;
    }
}
        
/**
 * Enqueues the node unless null. (Currently used only for
 * ConditionNodes; other cases are interleaved with acquires.)
 */
final void enqueue(Node node) {
    if (node != null) {
        for (;;) {
            Node t = tail;
            node.setPrevRelaxed(t);        // avoid unnecessary fence
            if (t == null)                 // initialize
                tryInitializeHead();
            else if (casTail(t, node)) {
                t.next = node;
                if (t.status < 0)          // wake up to clean link
                    //喚醒線程
                    LockSupport.unpark(node.waiter);
                break;
            }
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評(píng)論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,423評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容