久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

面經(jīng)手冊 · 第17篇《碼農(nóng)會鎖,ReentrantLock之AQS原理分析和實(shí)踐使用》

 小傅哥 2021-12-13


作者:小傅哥
博客:https://

沉淀,、分享,、成長,讓自己和他人都能有所收獲!😄

一,、前言

如果你相信你做什么都能成,你會自信的多!

千萬不要總自我否定,尤其是職場的打工人,。如果你經(jīng)常感覺,這個(gè)做不好,那個(gè)學(xué)不會,別的也不懂,那么久而久之會越來越缺乏自信。

一般說能成事的人都具有賭徒精神,在他們眼里只要做這事那就一定能成,當(dāng)然也有可能最后就沒成,但在整個(gè)過程中人的心態(tài)是良好的,每天都有一個(gè)飽滿的精神狀態(tài),孜孜不倦的奮斗著,。最后也就是這樣的斗志讓走在一個(gè)起點(diǎn)的小伙伴,有了差距,。

二、面試題

謝飛機(jī),小記,今天打工人呀,明天早上困呀,嘟嘟嘟,喂?誰呀,打農(nóng)藥呢!?

謝飛機(jī):哎呦,面試官大哥,咋了!

面試官:偷偷告訴你哈,你一面過了,。

謝飛機(jī):嘿嘿,真的呀!太好了!哈哈哈,那我還準(zhǔn)備點(diǎn)什么呢!?

面試官:二面會比較難嘍,嗯,我順便問你一個(gè)哈,。AQS 你了解嗎,ReentrantLock 獲取鎖的過程是什么樣的?什么是 CAS?…

謝飛機(jī):我我我,腦子還在后羿射箭里,我一會就看看!!

面試官:好好準(zhǔn)備下吧,打工人,打工魂!

三、ReentrantLock 和 AQS

1. ReentrantLock 知識鏈

ReentrantLock 可重入獨(dú)占鎖涉及的知識點(diǎn)較多,為了更好的學(xué)習(xí)這些知識,在上一章節(jié)先分析源碼和學(xué)習(xí)實(shí)現(xiàn)了公平鎖的幾種方案,。包括:CLH,、MCS、Ticket,通過這部分內(nèi)容的學(xué)習(xí),再來理解 ReentrantLock 中關(guān)于 CLH 的變體實(shí)現(xiàn)和相應(yīng)的應(yīng)用就比較容易了,。

接下來沿著 ReentrantLock 的知識鏈,繼續(xù)分析 AQS 獨(dú)占鎖的相關(guān)知識點(diǎn),如圖 17-1

圖 17-1 ReentrantLock 的知識鏈

在這部分知識學(xué)習(xí)中,會主要圍繞 ReentrantLock 中關(guān)于 AQS 的使用進(jìn)行展開,逐步分析源碼了解原理,。

AQS 是 AbstractQueuedSynchronizer 的縮寫,幾乎所有 Lock 都是基于 AQS 來實(shí)現(xiàn)了,其底層大量使用 CAS 提供樂觀鎖服務(wù),在沖突時(shí)采用自旋方式進(jìn)行重試,以此實(shí)現(xiàn)輕量級和高效的獲取鎖,。

另外 AbstractQueuedSynchronizer 是一個(gè)抽象類,但并沒有定義相應(yīng)的抽象方法,而是提供了可以被字類繼承時(shí)覆蓋的 protected 的方法,這樣就可以非常方便的支持繼承類的使用。

2. 寫一個(gè)簡單的 AQS 同步類

在學(xué)習(xí) ReentrantLock 中應(yīng)用的 AQS 之前,先實(shí)現(xiàn)一個(gè)簡單的同步類,來體會下 AQS 的作用,。

2.1 代碼實(shí)現(xiàn)

public class SyncLock {

    private final Sync sync;

    public SyncLock() {
        sync = new Sync();
    }

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            return compareAndSetState(0, 1);
        }

        @Override
        protected boolean tryRelease(int arg) {
            setState(0);
            return true;
        }

        // 該線程是否正在獨(dú)占資源,只有用到 Condition 才需要去實(shí)現(xiàn)
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

}

這個(gè)實(shí)現(xiàn)的過程屬于 ReentrantLock 簡版,主要包括如下內(nèi)容:

  1. Sync 類繼承 AbstractQueuedSynchronizer,并重寫方法:tryAcquire,、tryRelease、isHeldExclusively,。
  2. 這三個(gè)方法基本是必須重寫的,如果不重寫在使用的時(shí)候就會拋異常 UnsupportedOperationException,。
  3. 重寫的過程也比較簡單,主要是使用 AQS 提供的 CAS 方法。以預(yù)期值為 0,寫入更新值 1,寫入成功則獲取鎖成功,。其實(shí)這個(gè)過程就是對 state 使用 unsafe 本地方法,傳遞偏移量 stateOffset 等參數(shù),進(jìn)行值交換操作,。unsafe.compareAndSwapInt(this, stateOffset, expect, update)
  4. 最后提供 lock、unlock 兩個(gè)方法,實(shí)際的類中會實(shí)現(xiàn) Lock 接口中的相應(yīng)方法,這里為了簡化直接自定義這樣兩個(gè)方法,。

2.2 單元測試

@Test
public void test_SyncLock() throws InterruptedException {
    final SyncLock lock = new SyncLock();
    for (int i = 0; i < 10; i++) {
        Thread.sleep(200);
        new Thread(new TestLock(lock), String.valueOf(i)).start();
    }
    Thread.sleep(100000);
}

static class TestLock implements Runnable {
    private SyncLock lock;
    public TestLock(SyncLock lock) throws InterruptedException {
        this.lock = lock;
    }
    @Override
    public void run() {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println(String.format("Thread %s Completed", Thread.currentThread().getName()));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
  • 以上這個(gè)單元測試和我們在上一章節(jié)介紹公平鎖時(shí)是一樣的,驗(yàn)證順序輸出,。當(dāng)然你也可以選擇多線程操作一個(gè)方法進(jìn)行加和運(yùn)算。
  • 在測試的過程中可以嘗試把加鎖代碼注釋掉,進(jìn)行比對,。如果可以順序輸出,那么就是預(yù)期結(jié)果。

測試結(jié)果

Thread 0 Completed
Thread 1 Completed
Thread 2 Completed
Thread 3 Completed
Thread 4 Completed
Thread 5 Completed
Thread 6 Completed
Thread 7 Completed
Thread 8 Completed
Thread 9 Completed
  • 從測試結(jié)果看,以上 AQS 實(shí)現(xiàn)的同步類,滿足預(yù)期效果,。
  • 有了這段代碼的概念結(jié)構(gòu),接下來在分析 ReentrantLock 中的 AQS 使用就有一定的感覺了!

3. CAS 介紹

CAS 是 compareAndSet 的縮寫,它的應(yīng)用場景就是對一個(gè)變量進(jìn)行值變更,在變更時(shí)會傳入兩個(gè)參數(shù):一個(gè)是預(yù)期值,、另外一個(gè)是更新值。如果被更新的變量預(yù)期值與傳入值一致,則可以變更,。

CAS 的具體操作使用到了 unsafe 類,底層用到了本地方法 unsafe.compareAndSwapInt 比較交換方法,。

CAS 是一種無鎖算法,這種操作是 CPU 指令集操作,只有一步原子操作,速度非常快,。而且 CAS 避免了請求操作系統(tǒng)來裁定鎖問題,直接由 CPU 搞定,但也不是沒有開銷,比如 Cache Miss,感興趣的小伙伴可以自行了解 CPU 硬件相關(guān)知識,。

4. AQS 核心源碼分析

4.1 獲取鎖流程圖

圖 17-2 獲取鎖流程圖

圖 17-2 就是整個(gè) ReentrantLock 中獲取鎖的核心流程,包括非公平鎖和公平鎖的一些交叉流程。接下來我們就以此按照此流程來講解相應(yīng)的源碼部分,。

4.2 lock

圖 17-3 lock -> CAS

ReentrantLock 實(shí)現(xiàn)了非公平鎖和公平鎖,所以在調(diào)用 lock.lock(); 時(shí),會有不同的實(shí)現(xiàn)類:

  1. 非公平鎖,會直接使用 CAS 進(jìn)行搶占,修改變量 state 值,。如果成功則直接把自己的線程設(shè)置到 exclusiveOwnerThread,也就是獲得鎖成功。不成功后續(xù)分析
  2. 公平鎖,則不會進(jìn)行搶占,而是規(guī)規(guī)矩矩的進(jìn)行排隊(duì),。老實(shí)人

4.3 compareAndSetState

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

在非公平鎖的實(shí)現(xiàn)類里,獲取鎖的過程,有這樣一段 CAS 操作的代碼,。compareAndSetState 賦值成功則獲取鎖。那么 CAS 這里面做了什么操作?

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

往下翻我們看到這樣一段代碼,這里是 unsafe 功能類的使用,兩個(gè)參數(shù)到這里變成四個(gè)參數(shù),。多了 this,、stateOffset。this 是對象本身,那么 stateOffset 是什么?

stateOffset = unsafe.objectFieldOffset
    (AbstractQueuedSynchronizer.class.getDeclaredField("state"));

再往下看我們找到,stateOffset 是偏移量值,偏移量是一個(gè)固定的值,。接下來我們就看看這個(gè)值到底是多少!

引用POM jol-cli

<!-- https:///artifact/org.openjdk.jol/jol-cli -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-cli</artifactId>
    <version>0.14</version>
</dependency>

單元測試

@Test
public void test_stateOffset() throws Exception {
    Unsafe unsafe = getUnsafeInstance();
    long state = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
    System.out.println(state);
}

// 16
  • 通過 getUnsafeInstance 方法獲取 Unsafe,這是一個(gè)固定的方法,。
  • 在獲取 AQS 類中的屬性字段 state 的偏移量,16。
  • 除了這個(gè)屬性外你還可以拿到:headOffset,、tailOffset,、waitStatusOffset,、nextOffset,的值,最終自旋來變更這些變量的值。

4.4 (AQS)acquire

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

整個(gè)這塊代碼里面包含了四個(gè)方法的調(diào)用,如下:

  1. tryAcquire,分別由繼承 AQS 的公平鎖(FairSync),、非公平鎖(NonfairSync)實(shí)現(xiàn),。
  2. addWaiter,該方法是 AQS 的私有方法,主要用途是方法 tryAcquire 返回 false 以后,也就是獲取鎖失敗以后,把當(dāng)前請求鎖的線程添加到隊(duì)列中,并返回 Node 節(jié)點(diǎn)。
  3. acquireQueued,負(fù)責(zé)把 addWaiter 返回的 Node 節(jié)點(diǎn)添加到隊(duì)列結(jié)尾,并會執(zhí)行獲取鎖操作以及判斷是否把當(dāng)前線程掛起,。
  4. selfInterrupt,是 AQS 中的 Thread.currentThread().interrupt() 方法調(diào)用,它的主要作用是在執(zhí)行完 acquire 之前自己執(zhí)行中斷操作,。

4.5 tryAcquire

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            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;
}

這部分獲取鎖的邏輯比較簡單,主要包括兩部分:

  1. 如果 c == 0,鎖沒有被占用,嘗試使用 CAS 方式獲取鎖,并返回 true。
  2. 如果 current == getExclusiveOwnerThread(),也就是當(dāng)前線程持有鎖,則需要調(diào)用 setState 進(jìn)行鎖重入操作,。setState 不需要加鎖,因?yàn)槭窃谧约旱漠?dāng)前線程下,。
  3. 最后如果兩種都不滿足😌,則返回 false。

4.6 addWaiter

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    // 如果隊(duì)列不為空, 使用 CAS 方式將當(dāng)前節(jié)點(diǎn)設(shè)為尾節(jié)點(diǎn)
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 隊(duì)列為空,、CAS失敗,將節(jié)點(diǎn)插入隊(duì)列
    enq(node);
    return node;
}
  • 當(dāng)執(zhí)行方法 addWaiter,那么就是 !tryAcquire = true,也就是 tryAcquire 獲取鎖失敗了,。
  • 接下來就是把當(dāng)前線程封裝到 Node 節(jié)點(diǎn)中,加入到 FIFO 隊(duì)列中。因?yàn)橄冗M(jìn)先出,所以后來的隊(duì)列加入到隊(duì)尾
  • compareAndSetTail 不一定一定成功,因?yàn)樵诓l(fā)場景下,可能會出現(xiàn)操作失敗,。那么失敗后,則需要調(diào)用 enq 方法,該方法會自旋操作,把節(jié)點(diǎn)入隊(duì)列,。

enq

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;
            }
        }
    }
}
  • 自旋轉(zhuǎn)for循環(huán) + CAS 入隊(duì)列。
  • 當(dāng)隊(duì)列為空時(shí),則會新創(chuàng)建一個(gè)節(jié)點(diǎn),把尾節(jié)點(diǎn)指向頭節(jié)點(diǎn),然后繼續(xù)循環(huán),。
  • 第二次循環(huán)時(shí),則會把當(dāng)前線程的節(jié)點(diǎn)添加到隊(duì)尾,。head 節(jié)是一個(gè)無用節(jié)點(diǎn),這和我們做CLH實(shí)現(xiàn)時(shí)類似

注意,從尾節(jié)點(diǎn)逆向遍歷

  1. 首先這里的節(jié)點(diǎn)連接操作并不是原子,也就是說在多線程并發(fā)的情況下,可能會出現(xiàn)個(gè)別節(jié)點(diǎn)并沒有設(shè)置 next 值,就失敗了。
  2. 但這些節(jié)點(diǎn)的 prev 是有值的,所以需要逆向遍歷,讓 prev 屬性重新指向新的尾節(jié)點(diǎn),直至全部自旋入隊(duì)列,。

4.7 acquireQueued

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // 當(dāng)前節(jié)點(diǎn)的前驅(qū)就是head節(jié)點(diǎn)時(shí), 再次嘗試獲取鎖
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 獲取鎖失敗后, 判斷是否把當(dāng)前線程掛起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

當(dāng)獲取鎖流程走到這,說明節(jié)點(diǎn)已經(jīng)加入隊(duì)列完成,。看源碼中接下來就是讓該方法再次嘗試獲取鎖,如果獲取鎖失敗會判斷是否把線程掛起,。

setHead

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

在學(xué)習(xí) CLH 公平鎖數(shù)據(jù)結(jié)構(gòu)中講到Head節(jié)點(diǎn)是一個(gè)虛節(jié)點(diǎn),如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是Head節(jié)點(diǎn),那么說明此時(shí)Node節(jié)點(diǎn)排在隊(duì)列最前面,可以嘗試獲取鎖,。

獲取鎖后設(shè)置Head節(jié)點(diǎn),這個(gè)過程就是一個(gè)出隊(duì)列過程,原來節(jié)點(diǎn)設(shè)置Null方便GC。

shouldParkAfterFailedAcquire

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
    // SIGNAL 設(shè)置了前一個(gè)節(jié)點(diǎn)完結(jié)喚醒,安心干別的去了,這里是睡,。
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

你是否還CANCELLED,、SIGNAL、CONDITION ,、PROPAGATE ,這四種狀態(tài),在這個(gè)方法中用到了兩種如下:

  1. CANCELLED,取消排隊(duì),放棄獲取鎖,。
  2. SIGNAL,標(biāo)識當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)狀態(tài)已經(jīng)被掛起,意思就是大家一起排隊(duì)上廁所,隊(duì)伍太長了,后面的謝飛機(jī)說,我去買個(gè)油條哈,一會到我了,你微信我哈。其實(shí)就是當(dāng)前線程執(zhí)行完畢后,需要額外執(zhí)行喚醒后繼節(jié)點(diǎn)操作,。

那么,以上這段代碼主要的執(zhí)行內(nèi)容包括:

  1. 如果前一個(gè)節(jié)點(diǎn)狀態(tài)是 SIGNAL,則返回 true,。安心睡覺😪等著被叫醒
  2. 如果前一個(gè)節(jié)點(diǎn)狀態(tài)是 CANCELLED,就是它放棄了,則繼續(xù)向前尋找其他節(jié)點(diǎn)。
  3. 最后如果什么都沒找到,就給前一個(gè)節(jié)點(diǎn)設(shè)置個(gè)鬧鐘 SIGNAL,等著被通知,。

4.8 parkAndCheckInterrupt

if (shouldParkAfterFailedAcquire(p, node) &&
    parkAndCheckInterrupt())
    interrupted = true;

// 線程掛起等待被喚醒    
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}    
  • 當(dāng)方法 shouldParkAfterFailedAcquire 返回 false 時(shí),則執(zhí)行 parkAndCheckInterrupt() 方法,。
  • 那么,這一段代碼就是對線程的掛起操作,LockSupport.park(this);
  • Thread.interrupted() 檢查當(dāng)前線程的中斷標(biāo)識,。

四,、總結(jié)

  • ReentrantLock 的知識比較多,涉及的代碼邏輯也比較復(fù)雜,在學(xué)習(xí)的過程中需要對照源碼和相關(guān)并發(fā)書籍和資料一起學(xué)習(xí),以及最好的是自身實(shí)踐,。
  • AQS 的實(shí)現(xiàn)部分涉及的內(nèi)容較多,例如:state 屬性使用 unsafe 提供的本地方法進(jìn)行 CAS 操作,把初始值 0 改為 1,則獲得了鎖。addWaiter,、acquireQueued,、shouldParkAfterFailedAcquire、parkAndCheckInterrupt等,可以細(xì)致總結(jié),。
  • 所有的 Lock 都是基于 AQS 來實(shí)現(xiàn)了,。AQS 和 Condition 各自維護(hù)了不同的隊(duì)列,在使用 Lock 和 Condition 的時(shí)候,就是兩個(gè)隊(duì)列的互相移動。這句話可以細(xì)細(xì)體會,。可能文中會有一些不準(zhǔn)確或者錯(cuò)字,歡迎留言,我會不斷的更新博客,。

五、系列推薦

  • volatile 怎么實(shí)現(xiàn)的內(nèi)存可見?沒有 volatile 一定不可見嗎?
  • synchronized 解毒,剖析源碼深度分析!
  • ReentrantLock之公平鎖講解和實(shí)現(xiàn)
  • 咋嘞?你的IDEA過期了吧!加個(gè)Jar包就破解了,為什么?
  • 大學(xué)四年到畢業(yè)工作5年的學(xué)習(xí)路線資源匯總

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多