AQS之獨(dú)占鎖

簡(jiǎn)介

AbstractQueuedSynchronizer(簡(jiǎn)稱AQS)是JAVA中用來實(shí)現(xiàn)鎖的基礎(chǔ)框架拖吼,其內(nèi)部維護(hù)了一個(gè)等待隊(duì)列,用來存儲(chǔ)等待獲取鎖的線程巨坊。隊(duì)列遵循FIFO(先進(jìn)先出)的原則,每次入隊(duì)列都是添加到隊(duì)列的尾部,tail總是指向尾部節(jié)點(diǎn)檩帐;出隊(duì)列就是將head節(jié)點(diǎn)設(shè)置指向head的next節(jié)點(diǎn),head節(jié)點(diǎn)中沒有線程另萤,其代表當(dāng)前獲取到鎖的線程湃密。其可實(shí)現(xiàn)共享鎖和獨(dú)占鎖兩種方式的鎖。

+------+  prev +-----+       +-----+
| head | <---- |     | <---- | tail|  
+------+       +-----+       +-----+

隊(duì)列節(jié)點(diǎn)類

static final class Node {

    /** 共享模式標(biāo)識(shí) */
    static final Node SHARED = new Node();

    /** 獨(dú)占模式標(biāo)識(shí) */
    static final Node EXCLUSIVE = null;

    /** 節(jié)點(diǎn)狀態(tài)值四敞,表示線程被取消 */
    static final int CANCELLED =  1;

    /** 節(jié)點(diǎn)狀態(tài)值泛源,表示后一個(gè)節(jié)點(diǎn)需要被unparking喚醒 */
    static final int SIGNAL    = -1;

    /** 節(jié)點(diǎn)狀態(tài)值,表示在等待某一個(gè)條件目养,此節(jié)點(diǎn)在condition隊(duì)列中俩由,而不是sync隊(duì)列中 */
    static final int CONDITION = -2;

    /** 節(jié)點(diǎn)狀態(tài)值,喚醒可以向后傳遞癌蚁,共享模式時(shí)使用 */
    static final int PROPAGATE = -3;

    /**
     * 節(jié)點(diǎn)狀態(tài)幻梯,sync時(shí)默認(rèn)為0;condition時(shí)默認(rèn)初始化為Node.CONDITION
     */
    volatile int waitStatus;

    /**
     * 前一個(gè)節(jié)點(diǎn)
     */
    volatile Node prev;

    /**
     * 后一個(gè)節(jié)點(diǎn)
     */
    volatile Node next;

    /**
     * 節(jié)點(diǎn)代表的線程
     */
    volatile Thread thread;

    /**
     * sync時(shí)為標(biāo)識(shí)位
     */
    Node nextWaiter;

    /**
     * 節(jié)點(diǎn)是否為共享模式
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 返回當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)努释,前一個(gè)節(jié)點(diǎn)為空則拋異常
     *
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {
    }

    Node(Thread thread, Node mode) {     // addWaiter時(shí)使用
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Condition時(shí)使用
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

AQS中的屬性

/**
 * 等待隊(duì)列的頭結(jié)點(diǎn)碘梢,惰性初始化。除初始化時(shí)伐蒂,只能通過setHead方法修改煞躬。
 * 注意:如果頭結(jié)點(diǎn)存在,它的狀態(tài)確保不能是CANCELLED狀態(tài)逸邦。
 */
private transient volatile Node head;

/**
 * 等待隊(duì)列的尾結(jié)點(diǎn)恩沛,惰性初始化。只能通過enq方法添加新的等待節(jié)點(diǎn)
 */
private transient volatile Node tail;

/**
 * 同步狀態(tài)標(biāo)識(shí)
 */
private volatile int state;

AQS中的方法

  • enq缕减,即入隊(duì)列(enqueue)方法雷客,將節(jié)點(diǎn)插入到隊(duì)列的尾部
/**
 * 插入節(jié)點(diǎn)到隊(duì)列尾部,必要時(shí)需要進(jìn)行初始化
 * @param node 需要被插入的節(jié)點(diǎn)
 * @return 此節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)
 */
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // 尾節(jié)點(diǎn)為空桥狡,表示隊(duì)列未被初始化搅裙,設(shè)置頭節(jié)點(diǎn)為一個(gè)空的Node,并將tail指向頭結(jié)點(diǎn)裹芝。設(shè)置完后進(jìn)行下一次循環(huán)部逮,進(jìn)入else中
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t; //設(shè)置node的前節(jié)點(diǎn)為隊(duì)列的尾節(jié)點(diǎn)
            if (compareAndSetTail(t, node)) {
                t.next = node; //原子更新尾節(jié)點(diǎn)成功后,node成為新的尾節(jié)點(diǎn)tail嫂易,原來的尾節(jié)點(diǎn)t成為node的前節(jié)點(diǎn)兄朋,設(shè)置t的后節(jié)點(diǎn)為node
                return t;
            }
        }
    }
}
  • addWaiter,為當(dāng)前線程創(chuàng)建節(jié)點(diǎn)并加入到等待隊(duì)列中
/**
 * 為當(dāng)前線程創(chuàng)建節(jié)點(diǎn)并加入到隊(duì)列中
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) { // 與enq方法中的else中功能相同
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node); //尾節(jié)點(diǎn)tail為null或者更新node為尾節(jié)點(diǎn)失敗怜械,執(zhí)行此處
    return node;
}
  • setHead蜈漓,設(shè)置隊(duì)列的頭結(jié)點(diǎn)穆桂,也是出隊(duì)列。
/**
 * 設(shè)置隊(duì)列的頭結(jié)點(diǎn)融虽,也是出隊(duì)列享完。被acquire方法調(diào)用
 *  
 * @param node the node
 */
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}
  • unparkSuccessor,喚醒掛起的節(jié)點(diǎn)
private void unparkSuccessor(Node node) {
    /*
     * 當(dāng)前結(jié)點(diǎn)狀態(tài)小于0時(shí)有额,設(shè)置狀態(tài)為0
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
    * 喚醒的線程被successor持有般又,通常情況下就是下一個(gè)節(jié)點(diǎn)。但是如果被取消或者為null了巍佑,
    * 需要從隊(duì)列尾tail向前查找離node最近的未被取消的successor
    */
    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;
    }
    if (s != null)
        LockSupport.unpark(s.thread); //喚醒線程茴迁。【對(duì)同一線程先unpark再park萤衰,park是不會(huì)阻塞線程的堕义。】
}
  • cancelAcquire脆栋,取消正在嘗試獲取鎖的node
private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;

    // 跳過已取消的前節(jié)點(diǎn)
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // 如果node為尾節(jié)點(diǎn)倦卖,則將有效的前節(jié)點(diǎn)設(shè)置為尾節(jié)點(diǎn)
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null); //尾節(jié)點(diǎn)的next設(shè)置為null
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.

        int ws;
        if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) {
            // 1. pred不是頭節(jié)點(diǎn)
            // 2. pred的狀態(tài)為Node.SIGNAL或者狀態(tài)<=0同時(shí)可以設(shè)置為Node.SIGNAL狀態(tài)
            // 3. pred的持有的線程不為空
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next); //pred的next節(jié)點(diǎn)設(shè)置為當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
        } else {
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}
  • shouldParkAfterFailedAcquire,檢查與更新獲取鎖失敗的節(jié)點(diǎn)的狀態(tài)椿争,如果線程需要掛起怕膛,返回true.
/**
 *
 * 檢查與更新獲取鎖失敗的節(jié)點(diǎn)的狀態(tài),如果線程需要掛起秦踪,返回true.
 *
 * @param pred node's predecessor holding status
 * @param node the node
 * @return {@code true} if thread should block
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * 這個(gè)節(jié)點(diǎn)已經(jīng)設(shè)置為允許釋放鎖release來喚醒它的狀態(tài)褐捻,所以它可以被掛起
         */
        return true;
    if (ws > 0) {
        /*
         * 跳過所有已經(jīng)被取消的前節(jié)點(diǎn)
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * 此處的waitStatus狀態(tài)為0或者PROPAGATE。
         * 表示此節(jié)點(diǎn)需要一個(gè)喚醒信號(hào)椅邓,但是還沒有被掛起柠逞。
         * 調(diào)用者需要重試來確保它在掛起前不能獲取到鎖
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
  • selfInterrupt,中斷當(dāng)前線程
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}
  • parkAndCheckInterrupt景馁,掛起當(dāng)前線程并檢查是否中斷
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
  • acquireQueued边苹,對(duì)于已經(jīng)在隊(duì)列中的節(jié)點(diǎn),以獨(dú)占的方式獲取鎖
/**
 * 對(duì)于已經(jīng)在隊(duì)列中的節(jié)點(diǎn)裁僧,以獨(dú)占的方式獲取鎖。condition的wait和acquire方法會(huì)使用它
 *
 * @param node the node
 * @param arg the acquire argument
 * @return {@code true} if interrupted while waiting
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true; //是否失敗
    try {
        boolean interrupted = false; //是否中斷
        for (;;) {
            /**
             * 隊(duì)列中的所有節(jié)點(diǎn)都在此處循環(huán)調(diào)用慕购,
             * 只有pred為頭節(jié)點(diǎn)的節(jié)點(diǎn)可能獲取到鎖聊疲,
             * 這時(shí)重新設(shè)置獲取到鎖的節(jié)點(diǎn)為頭結(jié)點(diǎn)(獲取到鎖的節(jié)點(diǎn)出隊(duì)列)并返回
             */
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { //前節(jié)點(diǎn)為頭節(jié)點(diǎn)同時(shí)獲取鎖成功
                setHead(node); //將node節(jié)點(diǎn)設(shè)置為頭結(jié)點(diǎn)
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) //檢查是否需要被掛起
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • acquire,獲取獨(dú)占鎖
/**
 * 調(diào)用tryAcquire獲取鎖沪悲,如果成功則結(jié)束
 * 如果失敗获洲,則用addWaiter為當(dāng)前線程創(chuàng)建獨(dú)占鎖節(jié)點(diǎn)加入到等待隊(duì)列中
 * 調(diào)用acquireQueued方法不斷循環(huán)檢查是否能獲取到鎖或者掛起線程,直到獲取到鎖后返回
 * 可用來實(shí)現(xiàn)Lock#lock方法
 * @param arg
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • release殿如,釋放鎖
**
 * 釋放鎖贡珊。
 * 可用來實(shí)現(xiàn){@link Lock#unlock}
 *
 * @param arg arg the release argument.  This value is conveyed to
 *        {@link #tryRelease} but is otherwise uninterpreted and
 *        can represent anything you like.
 * @return the value returned from {@link #tryRelease}
 */
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        /*
         * 釋放鎖成功最爬,頭結(jié)點(diǎn)不為空且狀態(tài)不為0,則喚醒頭結(jié)點(diǎn)后面節(jié)點(diǎn)的線程门岔;
         * 喚醒后爱致,被喚醒的線程則在acquireQueued方法的for循環(huán)里循環(huán)獲取鎖
         */
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
  • tryAcquire, 需子類實(shí)現(xiàn)寒随,以獨(dú)占的方式獲取鎖
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
  • tryRelease糠悯,需子類實(shí)現(xiàn),釋放獨(dú)占所
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妻往,一起剝皮案震驚了整個(gè)濱河市互艾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讯泣,老刑警劉巖纫普,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異好渠,居然都是意外死亡昨稼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門晦墙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悦昵,“玉大人,你說我怎么就攤上這事晌畅》郧” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵朋鞍,是天一觀的道長(zhǎng)队橙。 經(jīng)常有香客問我,道長(zhǎng)连躏,這世上最難降的妖魔是什么剩岳? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮入热,結(jié)果婚禮上拍棕,老公的妹妹穿的比我還像新娘。我一直安慰自己勺良,他們只是感情好绰播,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尚困,像睡著了一般蠢箩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天谬泌,我揣著相機(jī)與錄音滔韵,去河邊找鬼。 笑死掌实,一個(gè)胖子當(dāng)著我的面吹牛陪蜻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播潮峦,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼囱皿,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了忱嘹?” 一聲冷哼從身側(cè)響起嘱腥,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拘悦,沒想到半個(gè)月后齿兔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡础米,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年分苇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屁桑。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡医寿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蘑斧,到底是詐尸還是另有隱情靖秩,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布竖瘾,位于F島的核電站沟突,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏捕传。R本人自食惡果不足惜惠拭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庸论。 院中可真熱鬧职辅,春花似錦、人聲如沸聂示。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)催什。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒲凶,已是汗流浹背气筋。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旋圆,地道東北人宠默。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像灵巧,于是被迫代替她去往敵國(guó)和親搀矫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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