成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

線程系列四AQS

sevi_stuo / 3312人閱讀

摘要:我們可以將的作用理解為在多線程的環(huán)境下保證線程等待獲取鎖添加進(jìn)入隊(duì)列以及線程獲取鎖,并隊(duì)列中出去都是線程安全的。是如何做到線程安全的主要是通過(guò)死循環(huán)以及狀態(tài)值,來(lái)做到線程安全。

1、什么是aqs

aqs是一個(gè)FIFO的雙向鏈表隊(duì)列。aqs將等待獲取鎖的線程封裝成結(jié)點(diǎn),放在隊(duì)列中。

我們可以將aqs的作用理解為在多線程的環(huán)境下保證線程等待獲取鎖(添加進(jìn)入隊(duì)列)以及線程獲取鎖,并隊(duì)列中出去都是線程安全的。

更簡(jiǎn)單的可以理解為aqs為了保證在多線程的環(huán)境下入隊(duì)列出隊(duì)列線程安全性提供了一個(gè)基本功能框架。

2、aqs是如何做到線程安全的

aqs主要是通過(guò)cas + 死循環(huán)以及state狀態(tài)值,來(lái)做到線程安全。

3、aqs為什么會(huì)被設(shè)計(jì)為FIFO雙向鏈表隊(duì)列(以下是個(gè)人理解)
①aqs的鎖實(shí)現(xiàn),包含公平鎖和非公平鎖。為了實(shí)現(xiàn)公平鎖,必須使用隊(duì)列來(lái)保證獲取鎖的順序(入隊(duì)列的順序)

②用鏈表的方式,主要是因?yàn)?,操作更多是刪除與增加。鏈表時(shí)間復(fù)雜度O(1)的效率會(huì)比數(shù)組O(n)的低。

③用雙向隊(duì)列的原因是,aqs的設(shè)計(jì)思想,或則說(shuō)為了解決羊群效應(yīng)(為了爭(zhēng)奪鎖,大量線程同時(shí)被喚醒)。每個(gè)結(jié)點(diǎn)(線程)只需要關(guān)心自己的前一個(gè)結(jié)點(diǎn)的狀態(tài)(后續(xù)會(huì)說(shuō)),線程喚醒也只喚醒隊(duì)頭等待線程。

請(qǐng)參考 http://www.importnew.com/2400...

4、aqs是如何提供一個(gè)基礎(chǔ)框架的

aqs 通過(guò)模板設(shè)計(jì)進(jìn)行提供的,實(shí)現(xiàn)類只需實(shí)現(xiàn)特定的方法即可。

以下是aqs的模板方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

。。。 其他的省略了

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

tryAcquire(int arg)tryRelease(int arg) 是我們要實(shí)現(xiàn)的模板方法,當(dāng)然還有分享鎖的,這里只介紹了獨(dú)占鎖的。

5、從源碼角度剖析aqs。aqs是如何通過(guò)雙向鏈表隊(duì)列,cas,state狀態(tài)值,以及結(jié)點(diǎn)狀態(tài)來(lái)保證入隊(duì)列出隊(duì)列的線程安全的!

注:以下只介紹獨(dú)占式的不公平鎖

①aqs 如何獲取鎖?

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

tryAcquire(arg) 內(nèi)部調(diào)用了nonfairTryAcquire(int acquires)

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {  // 鎖未被獲取
         // cas(自旋) 獲取鎖,并修改state 狀態(tài)值
        if (compareAndSetState(0, acquires)) {
         // 設(shè)置當(dāng)前占有的線程
            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;
}

解釋:利用cas自旋式的獲取鎖。

②aqs 獲取鎖失敗,如何處理?

在看代碼前,先解釋一下:將當(dāng)前線程包裝成Node結(jié)點(diǎn),并插入同步隊(duì)列中,并用CAS形式嘗試獲取鎖,獲取失敗,則掛起當(dāng)前線程(以上只是說(shuō)了大概)

先看第1個(gè)方法(將當(dāng)前線程包裝成Node結(jié)點(diǎn),并插入同步隊(duì)列)

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) {  // 尾節(jié)點(diǎn)不為空
        node.prev = pred;
        // 用 CAS 將當(dāng)前線程插入隊(duì)尾
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 尾節(jié)點(diǎn)為空,說(shuō)明當(dāng)前隊(duì)列還是空的,需要初始化
    enq(node);
    return node;
}

private Node enq(final Node node) {
    // 死循環(huán)
    for (;;) {
        // 初始化
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else { // 這里主要是擔(dān)心有多個(gè)線程同時(shí)進(jìn)到enq(final Node node) 方法
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

解釋:隊(duì)列若為空,先初始化,不為空,用 CAS 將當(dāng)前結(jié)點(diǎn)插入到隊(duì)尾

再看第二個(gè)方法final boolean acquireQueued(final Node node, int arg);

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 獲取前置結(jié)點(diǎn)
            final Node p = node.predecessor();
            // 前置結(jié)點(diǎn)是頭結(jié)點(diǎn),則嘗試獲取鎖
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 前置結(jié)點(diǎn)不是頭結(jié)點(diǎn) 或者 前置結(jié)點(diǎn)是頭結(jié)點(diǎn)但是嘗試獲取鎖失敗
            // 則,應(yīng)當(dāng)將當(dāng)前線程掛起(畢竟不能一直死循環(huán)獲取吧~)
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 當(dāng)前線程的前置節(jié)點(diǎn)的狀態(tài)?。?!
    // 第waitStatus 初始化值為0,
    // 也因此當(dāng)?shù)?次進(jìn)到這個(gè)方法時(shí),會(huì)將前置結(jié)點(diǎn)的狀態(tài)置為 Node.SIGNAL。
    // 第 2次進(jìn)來(lái)的時(shí)候,前置節(jié)點(diǎn)的waitStatus的狀態(tài)就為 Node.SIGNAL)。
    // 也就是說(shuō)。aqs 只會(huì)讓你嘗試2次,都失敗后,就會(huì)被掛起
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don"t park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

// 線程被掛起調(diào)用該方法?。?private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

讓我們總結(jié)一下,以及再回顧一下,為什么aqs會(huì)被設(shè)計(jì)為雙向鏈表隊(duì)列。

aqs為了保證結(jié)點(diǎn)(即線程)的入隊(duì)列的安全。采用了CAS 以及死循環(huán)的方式(從代碼中可看到,處處使用CAS)。
上面有說(shuō)到,一個(gè)線程是否該被喚醒或者其他操作,只需要看前置結(jié)點(diǎn)的狀態(tài)即可。從shouldParkAfterFailedAcquire() 方法就可以看出這個(gè)設(shè)計(jì)。當(dāng)前線程該做什么操作,是看前置結(jié)點(diǎn)的狀態(tài)的。

③aqs如何釋放鎖

看代碼前,先解釋一下,aqs是如何做的。aqs的做法就是,釋放當(dāng)前鎖,然后喚醒頭結(jié)點(diǎn)的后繼結(jié)點(diǎn),如果后繼結(jié)點(diǎn)為空,或者是被取消的,則從尾節(jié)點(diǎn)向前尋找一個(gè)未被取消的結(jié)點(diǎn)

public final boolean release(int arg) {
    // 嘗試釋放鎖
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 喚醒后繼結(jié)點(diǎn)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

①ReentratLock 是如何實(shí)現(xiàn)鎖的釋放的

注:這里看的是ReentrantLock的實(shí)現(xiàn)

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

解釋:設(shè)置 state 的狀態(tài),如果 state == 0, 那么說(shuō)明鎖被釋放了。否則鎖還未被釋放(鎖重入?。?/p>

②aqs 如何喚醒其他結(jié)點(diǎn)

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
     // 清除狀態(tài),還記得等待的線程會(huì)把前置節(jié)點(diǎn)的狀態(tài)置為 Node.SIGNAL(-1)嗎
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
     // 正常情況下,下一個(gè)結(jié)點(diǎn)就是被喚醒的節(jié)點(diǎn)。
     // 但是如果下一個(gè)結(jié)點(diǎn)為null, 或者是被取消的
     // 那么從尾節(jié)點(diǎn)向前查找一個(gè)未被取消的節(jié)點(diǎn)喚醒。
    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);
}

release的釋放比較簡(jiǎn)單。還是可以看到,aqs被設(shè)計(jì)成雙向鏈表隊(duì)列的好處?。?!

看源代碼,不能一下子就扎進(jìn)去看,要先明白個(gè)大概,為什么看源代碼?還不是為了學(xué)習(xí)作者是如何設(shè)計(jì)的。細(xì)節(jié)無(wú)論誰(shuí)都記不清,最主要的是知道一個(gè)整體的流程,關(guān)鍵的代碼!畢竟優(yōu)秀的開源項(xiàng)目這么多,難道每行代碼都看??

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76678.html

相關(guān)文章

  • Java多線程進(jìn)階(十)—— J.U.C之locks框架:基于AQS的讀寫鎖(5)

    摘要:關(guān)于,最后有兩點(diǎn)規(guī)律需要注意當(dāng)?shù)牡却?duì)列隊(duì)首結(jié)點(diǎn)是共享結(jié)點(diǎn),說(shuō)明當(dāng)前寫鎖被占用,當(dāng)寫鎖釋放時(shí),會(huì)以傳播的方式喚醒頭結(jié)點(diǎn)之后緊鄰的各個(gè)共享結(jié)點(diǎn)。當(dāng)?shù)牡却?duì)列隊(duì)首結(jié)點(diǎn)是獨(dú)占結(jié)點(diǎn),說(shuō)明當(dāng)前讀鎖被使用,當(dāng)讀鎖釋放歸零后,會(huì)喚醒隊(duì)首的獨(dú)占結(jié)點(diǎn)。 showImg(https://segmentfault.com/img/remote/1460000016012293); 本文首發(fā)于一世流云的專欄:...

    dunizb 評(píng)論0 收藏0
  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過(guò)長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問(wèn)題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...

    lijy91 評(píng)論0 收藏0
  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過(guò)長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問(wèn)題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...

    Yumenokanata 評(píng)論0 收藏0
  • Java多線程進(jìn)階(六)—— J.U.C之locks框架:AQS綜述(1)

    摘要:在時(shí),引入了包,該包中的大多數(shù)同步器都是基于來(lái)構(gòu)建的??蚣芴峁┝艘惶淄ㄓ玫臋C(jī)制來(lái)管理同步狀態(tài)阻塞喚醒線程管理等待隊(duì)列。指針用于在結(jié)點(diǎn)線程被取消時(shí),讓當(dāng)前結(jié)點(diǎn)的前驅(qū)直接指向當(dāng)前結(jié)點(diǎn)的后驅(qū)完成出隊(duì)動(dòng)作。 showImg(https://segmentfault.com/img/remote/1460000016012438); 本文首發(fā)于一世流云的專欄:https://segmentfau...

    cocopeak 評(píng)論0 收藏0
  • Java多線程進(jìn)階(二十)—— J.U.C之synchronizer框架:Semaphore

    摘要:當(dāng)線程使用完共享資源后,可以歸還許可,以供其它需要的線程使用。所以,并不會(huì)阻塞調(diào)用線程。立即減少指定數(shù)目的可用許可數(shù)。方法用于將可用許可數(shù)清零,并返回清零前的許可數(shù)六的類接口聲明類聲明構(gòu)造器接口聲明 showImg(https://segmentfault.com/img/bVbfdnC?w=1920&h=1200); 本文首發(fā)于一世流云的專欄:https://segmentfault...

    boredream 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<