自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官:說下你對AQS的理解!

開發(fā) 前端
AQS,是?AbstractQueuedSynchronizer(抽象隊(duì)列同步器)這個(gè)類的簡稱,也是 Java JUC 包中的靈魂,ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch、CyclicBarrier 都是通過其實(shí)現(xiàn)鎖或同步器的。

在技術(shù)面試的時(shí)候,“說下你對 AQS 的理解”,這個(gè)問題出現(xiàn)的概率屬實(shí)不低,而一些技術(shù)底子一般的同學(xué),又非常容易被它復(fù)雜的底層源碼弄得暈頭轉(zhuǎn)向。

今天這篇文章,我們就以做減法的方式,將這個(gè)知識(shí)點(diǎn)徹底地大家講清楚。

AQS,是 AbstractQueuedSynchronizer(抽象隊(duì)列同步器)這個(gè)類的簡稱,也是 Java JUC 包中的靈魂,ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch、CyclicBarrier 都是通過其實(shí)現(xiàn)鎖或同步器的。

其核心思想為,在多線程并發(fā)訪問共享資源時(shí),通過雙向鏈表實(shí)現(xiàn)的先進(jìn)先出 CLH 隊(duì)列進(jìn)行線程排隊(duì),并通過由 volatile 修飾的 state 變量來標(biāo)識(shí)資源的鎖占用狀態(tài)。

如下圖所示:

圖片圖片

在 AQS 中提供了兩種資源獲取方式:

獨(dú)占模式(Exclusive),在同一時(shí)刻只能有一個(gè)線程獲取競態(tài)資源,比如:ReentrantLock。

共享模式(Share),在同一時(shí)刻可以有多個(gè)(參數(shù)設(shè)定)線程獲取競態(tài)資源,比如:CountDownLatch、Semaphore。

AQS 方法詳述

AQS 的方法大致分為三類,分別是獨(dú)占模式下的方法、共享模式下的方法、通過模板方法模式留給子類實(shí)現(xiàn)的方法。

獨(dú)占模式:

// 獲取鎖
public final void acquire(int arg) 
// 以可中斷的方式獲取鎖
public final void acquireInterruptibly(int arg)
// 以帶超時(shí)時(shí)間的方式,嘗試獲取鎖
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
// 釋放鎖
public final boolean release(int arg)

共享模式:

// 獲取鎖
public final void acquireShared(int arg)
// 以可中斷的方式獲取鎖
public final void acquireSharedInterruptibly(int arg)
// 以帶超時(shí)時(shí)間的方式,嘗試獲取鎖
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
// 釋放鎖
public final boolean releaseShared(int arg)

需要子類實(shí)現(xiàn)的方法:

// 嘗試獲取獨(dú)占鎖
protected boolean tryAcquire(int arg);
// 嘗試釋放獨(dú)占鎖
protected boolean tryRelease(int arg);
// 嘗試獲取共享鎖
protected int tryAcquireShared(int arg);
// 嘗試釋放共享鎖
protected boolean tryReleaseShared(int arg);
// 判斷當(dāng)前線程是否正在持有鎖
protected boolean isHeldExclusively();

看到 AQS 父類實(shí)現(xiàn)了一部分方法,也預(yù)留了一些方法讓 ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier 等子類實(shí)現(xiàn),我們想到了哪種設(shè)計(jì)模式?

是的,模板方法模式。

模板方法模式:定義一個(gè)操作中算法的框架,而將一些步驟延遲到子類中,模板方法使得子類可以不改變一個(gè)算法的結(jié)構(gòu),即可重定義該算法的某些步驟。

使用模板方法模式,可以將一個(gè)操作的復(fù)雜流程的實(shí)現(xiàn)步驟進(jìn)行拆分,封裝在一系列基本方法中,在抽象父類提供一個(gè)模板方法來定義整體執(zhí)行步驟,而通過其子類來覆蓋某個(gè)步驟,從而使得相同的執(zhí)行步驟可以有不同的執(zhí)行結(jié)果。

類結(jié)構(gòu)如下:

圖片圖片

模板方法模式的優(yōu)點(diǎn)在于:

  • 代碼復(fù)用性高,父類的模板方法和具體方法都可以供多個(gè)子類復(fù)用。
  • 代碼靈活性高,可根據(jù)業(yè)務(wù)迭代情況,靈活選擇哪部分復(fù)用父類具體方法,哪部分進(jìn)行子類覆蓋實(shí)現(xiàn)。

嗯,這些底層源碼的設(shè)計(jì)還是非常巧妙的,而設(shè)計(jì)模式本身也并不是有些人口中的過度設(shè)計(jì)的“花架子”。

ReentrantLock 與 AQS 

接下來我們看下,ReentrantLock 是如何通過 AQS 來實(shí)現(xiàn)鎖機(jī)制的。

兩者間的 UML 圖如下所示:

圖片圖片

從圖中可以看到,ReentrantLock 中有一個(gè) Sync 內(nèi)部類,Sync 繼承自 AQS,且 Sync 有兩個(gè)子類 FairSync 和 NonfairSync,分別用于實(shí)現(xiàn)公平鎖和非公平鎖。

我們梳理一下源碼,看看 ReentrantLock 如何實(shí)現(xiàn)非公平鎖的。

代碼入口如下,我們看只有兩個(gè)方法加上一個(gè)判斷。

public class ReentrantLock implements Lock, java.io.Serializable{


    abstract static class Sync extends AbstractQueuedSynchronizer {
        final void lock() {    
            if (!initialTryLock())
                acquire(1);
            }
        }
    }     
}

來看下該方法的具體實(shí)現(xiàn),簡而言之,該方法嘗試以獨(dú)占的方式獲取鎖。

static final class NonfairSync extends Sync {    
    final boolean initialTryLock() {        
        Thread current = Thread.currentThread();        
        if (compareAndSetState(0, 1)) {
            // first attempt is unguarded            
            setExclusiveOwnerThread(current);            
            return true;        
        } else if (getExclusiveOwnerThread() == current) {
            int c = getState() + 1;            
            if (c < 0) // overflow                
                throw new Error("Maximum lock count exceeded");      
            setState(c);            
            return true;        
        } else            
            return false;   
     }
 }

先是通過 compareAndSetState(0, 1) 方法,以原子操作的方式將 AQS 類中的 state 變量值從 0 修改到 1。

我們在上文中提到過,state 變量來標(biāo)識(shí)資源的鎖占用狀態(tài),0 代表未占用,1 代表已占用,大于 1 則代表鎖被重入,那么該操作就是嘗試獲取鎖。

若該操作執(zhí)行成功,則通過 setExclusiveOwnerThread(current) 作用是將當(dāng)前線程設(shè)置為持有獨(dú)占鎖的線程,并返回 true,代表獲取鎖成功了。

再往下分析 getExclusiveOwnerThread() == current,這是判斷當(dāng)前線程是否已獲取該鎖且處于未釋放的狀態(tài),若判斷成立則將 state 變量+1代表重入,并返回 true 表示獲取鎖成功。

btw:從這段代碼邏輯上看,知道為什么叫非公平鎖了吧,一上來并沒有通過 AQS 排隊(duì),而且先去爭搶鎖。

接下來我們繼續(xù)來看acquire(1)方法,代碼如下:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
    public final void acquire(int arg) {
        if (!tryAcquire(arg))
            acquire(null, arg, false, false, false, 0L);
    }
}

方法體重有兩個(gè)方法加上一個(gè)判斷,先來看 tryAcquire(arg) 方法的執(zhí)行邏輯。

static final class NonfairSync extends Sync {
    protected final boolean tryAcquire(int acquires) {
        if (getState() == 0 && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}

這塊代碼的邏輯竟然跟上面 initialTryLock() 方法的前半段幾乎一樣,先是通過 compareAndSetState(0, 1) 方法將 AQS 類中的 state 變量值從 0 修改到 1。

若該操作執(zhí)行成功,則通過 setExclusiveOwnerThread(current) 作用是將當(dāng)前線程設(shè)置為持有獨(dú)占鎖的線程,并返回 true,代表獲取鎖成功了。

btw:果然是非公平鎖啊,這是誓要將插隊(duì)爭搶鎖進(jìn)行到底了。

下面就是 AQS 中的重頭戲了,acquire(null, arg, false, false, false, 0L)方法,實(shí)現(xiàn)排隊(duì)獲取鎖。

代碼實(shí)現(xiàn)如下:

圖片圖片

這塊代碼并非主業(yè)務(wù)鏈路,先是進(jìn)行了三個(gè)判斷,當(dāng)前節(jié)點(diǎn)不是 first 節(jié)點(diǎn)和 head 節(jié)點(diǎn),且前驅(qū)結(jié)點(diǎn)不為null。

btw:head 節(jié)點(diǎn)可以理解為“當(dāng)前持有獨(dú)占鎖的線程”,而在 head 節(jié)點(diǎn)之后的線程都處于阻塞、等待被喚醒的狀態(tài),而 first 節(jié)點(diǎn)則是同步隊(duì)列中第一個(gè)等待獲取鎖的線程。

接下來 pred.status < 0 代表前驅(qū)節(jié)點(diǎn)已經(jīng)被取消,結(jié)果為 true 則做一次等待隊(duì)列清理。

而 pred.prev == null 則是判斷前驅(qū)節(jié)點(diǎn)是否為 null,結(jié)果為 true 則跳到下一次循環(huán)中。

圖片圖片

這段代碼的意思是,在當(dāng)前節(jié)點(diǎn)為 first 節(jié)點(diǎn)或前驅(qū)節(jié)點(diǎn)為 null (未入隊(duì))的情況下,繼續(xù)通過 tryAcquire(arg) 方法嘗試獲取鎖。

圖片圖片

這段代碼看起來比較復(fù)雜,其實(shí)也是有邏輯性的。

1、前兩個(gè)大的邏輯分支判斷的意思是,先創(chuàng)建一個(gè)獨(dú)占節(jié)點(diǎn),并將該節(jié)點(diǎn)加入到 CLH 隊(duì)列的尾部。

2、如果當(dāng)前節(jié)點(diǎn)為 first 節(jié)點(diǎn),且自旋數(shù)大于 0,則繼續(xù)嘗試自旋獲取鎖。

3、將當(dāng)前節(jié)點(diǎn)的狀態(tài)值設(shè)置為“等待中”。

4、當(dāng)前節(jié)點(diǎn)自旋失敗,使用 LockSupport.pack() 方法掛起線程。

5、當(dāng)獨(dú)占鎖被釋放,隊(duì)列中的 first 節(jié)點(diǎn)的線程將被喚醒,清除當(dāng)前節(jié)點(diǎn)的等待狀態(tài)。

責(zé)任編輯:武曉燕 來源: 托尼學(xué)長
相關(guān)推薦

2021-11-25 10:18:42

RESTfulJava互聯(lián)網(wǎng)

2021-08-09 07:47:40

Git面試版本

2020-12-01 08:47:36

Java異常開發(fā)

2025-02-21 15:25:54

虛擬線程輕量級(jí)

2024-09-27 15:43:52

零拷貝DMAIO

2020-06-12 15:50:56

options前端服務(wù)器

2022-03-21 09:05:18

volatileCPUJava

2025-03-21 00:00:05

Reactor設(shè)計(jì)模式I/O 機(jī)制

2024-10-24 16:14:43

數(shù)據(jù)傳輸CPU零拷貝

2020-06-19 15:32:56

HashMap面試代碼

2024-08-27 12:36:33

2024-06-13 08:01:19

2025-03-07 00:00:10

2019-07-26 06:42:28

PG架構(gòu)數(shù)據(jù)庫

2024-10-12 16:25:12

2024-09-26 16:01:52

2021-09-16 07:52:18

算法應(yīng)用場景

2024-08-26 14:52:58

JavaScript循環(huán)機(jī)制

2019-05-10 10:50:04

Spring AOPJDK動(dòng)態(tài)代理CGLIB動(dòng)態(tài)代理

2021-09-26 15:34:21

內(nèi)部結(jié)構(gòu)方法區(qū)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)