欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java并發(fā)框架中的AQS詳細(xì)解析

 更新時間:2024年01月29日 11:04:36   作者:Smallc0de  
這篇文章主要介紹了Java并發(fā)框架中的AQS詳細(xì)解析,之前說鎖的升級的時候,說到了自旋鎖會空轉(zhuǎn)幾次嘗試等待獲取資源,其實這一系列的動作是有一個規(guī)范的這個規(guī)范叫做同步發(fā)生器AbstractQueuedSynchronizer ,簡稱AQS,需要的朋友可以參考下

前言

之前說鎖的升級的時候,說到了自旋鎖會空轉(zhuǎn)幾次嘗試等待獲取資源,其實這一系列的動作是有一個規(guī)范的這個規(guī)范叫做同步發(fā)生器AbstractQueuedSynchronizer ,簡稱AQS。

同步發(fā)生器是用來構(gòu)建鎖用的,可以說是Java中同步組件的基礎(chǔ),在Java JDK的JUC包中:java.util.concurrent。

我們常用的ReentrantLock,Semaphore等等用的都是這樣一個架構(gòu),可以說是這些Lock工具的一個基礎(chǔ)。

AQS基本思想

AQS是一個隊列管理器,通過內(nèi)置的FIFO同步隊列去管理線程爭奪資源的。

FIFO就是先進(jìn)先出(Frist In First Out)的縮寫。

其實現(xiàn)核心是用到了CHL同步隊列,其核心思想就是:把每一個線程看作一個節(jié)點Node,然后給這些節(jié)點上加上前驅(qū)指針和后繼指針,這樣一來就可以用指針把這些線程節(jié)點(Thread Node)連接起來形成一個雙向鏈表,或者說雙向隊列。

除此之外,還有一個同步器節(jié)點(Synchronized Node),用來管理這些線程節(jié)點。

同步器節(jié)點也有兩個指針,第一個指針指向隊列首節(jié)點,第二個指針指向隊列尾節(jié)點。

因此同步器節(jié)點是事實上的頭節(jié)點Head,下圖就是一個完整的CHL同步隊列示意圖。

在這里插入圖片描述

注:CHL是人名簡稱沒啥具體意義。

AQS操作同步隊列

同步隊列有了,誰能拿到資源則是由一個狀態(tài)變量(state)來確定的。

當(dāng)state==0時,表示當(dāng)前資源沒有線程占用;當(dāng)state>=1時,表示當(dāng)前資源已經(jīng)被占用了,其他線程必須等待資源釋放。

假設(shè)有一個線程要使用資源,首先先會去檢查state變量獲取結(jié)果,如果state==0說明該線程可以使用請求資源,不需要排隊,直接取出線程節(jié)點去執(zhí)行;如果state>=1,說明該資源已經(jīng)被前面的線程拿走了,就必須要排隊。

在這里插入圖片描述

既然有個這個概念,所以說每一個線程節(jié)點都會做這樣的事情:獲取鎖和釋放鎖。

每個在隊列里面的縣城節(jié)點,都會不斷地自旋,每次自旋結(jié)束都會嘗試獲取鎖,如果獲取不到那么繼續(xù)自旋。

由于是FIFO先進(jìn)先出這種公平模式,因此線程頭節(jié)點總會第一個獲取到鎖,以此類推。

這里所有的節(jié)點都在[ 嘗試獲取鎖 – 自旋 ] 這種狀態(tài)不斷地重復(fù)。但是由于使用FIFO模式,只有頭節(jié)點的自旋是有意義的,其他的就是在空轉(zhuǎn)。

在這里插入圖片描述

這樣做有什么好處呢?假設(shè)我們把所有沒有獲取到資源的線程都掛起,這就必然要經(jīng)過用戶線程和核心(系統(tǒng))線程之間的切換,這種切換是非常耗時的。

由于CPU執(zhí)行的會很快,所以預(yù)期就是自旋幾次以后,就可以拿到想要的鎖,以規(guī)避線程之間的切換。

AQS 的用法

上面說過AbstractQueuedSynchronizer是一個框架,它能干什么用還得祭出官方文檔一探究竟,官方文檔很長,我們截取兩句最重要的:

Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues. 這句話是對AbstractQueuedSynchronizer定義,翻譯過來就是說:為那些想要依賴于FIFO等待隊列的阻塞鎖和相關(guān)的同步器(semaphores, events, 等等) 提供一個實施框架。 Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class. 這句話是用法的概括:其子類應(yīng)該被定義為非共有的內(nèi)部幫助器(助手類),用于實現(xiàn)外部類的屬性同步。

說白了AbstractQueuedSynchronizer就是Java給開發(fā)人員提供一個獲取鎖和釋放鎖的模板,用來處理synchronized封鎖粒度過大的問題。

它的主要功能方法如下:

Modifier and TypeMethodDescription
voidacquire(int arg)Acquires in exclusive mode, ignoring interrupts. 獨占模式獲取對象,忽略中斷。
voidacquireShared(int arg)Acquires in shared mode, ignoring interrupts. 共享模式獲取對象,忽略中斷。
booleanrelease(int arg)Releases in exclusive mode.以獨占模式釋放對象。
booleanreleaseShared(int arg)Releases in shared mode.以共享模式釋放對象。
protected booleantryAcquire(int arg)Attempts to acquire in exclusive mode.試圖以獨占模式獲取鎖,這個就是自旋的方法,一直試探
protected inttryAcquireShared(int arg)Attempts to acquire in shared mode. 試圖以共享模式獲取鎖。

說明:共享模式下,當(dāng)一個線程獲取了鎖,其他線程依然可讀取信息。獨占模式下,線程獨占了鎖,不許其他線程使用。

源碼解析AQS

上面已經(jīng)AQS原理和常用方法說完了,總要有一個地方體現(xiàn)吧。我們可以打開一個方法看看Java中AQS獲取鎖是不是和我們說的原理一致。首先進(jìn)入acquire()方法。

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

進(jìn)入以后先判斷tryAcquire(arg)這里面只有一個拋異常不多說了。然后調(diào)用acquireQueued()方法,進(jìn)入。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) { //空轉(zhuǎn),自旋
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { //如果當(dāng)前是頭節(jié)點,繼續(xù)嘗試獲取鎖,這也就是為啥只有頭節(jié)點才有意義
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
		//如果不是繼續(xù)等待
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

發(fā)現(xiàn)這個方法已經(jīng)要求傳入的參數(shù)就是Node,其實就是把要等待的Node繼續(xù)等待,那么返回上去,看看addWaiter()又寫了啥。

private Node addWaiter(Node mode) {
	//把當(dāng)前線程改造稱為Node,
    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) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

進(jìn)入就發(fā)現(xiàn)用構(gòu)建Node用的就是當(dāng)前線程Thread.currentThread(),這就是線程節(jié)點的由來。

實現(xiàn)一個鎖類

在Java的JUC包中同樣為了這樣一個模板提供了一個實現(xiàn)接口就是Lock接口,我們常用的ReentrantLock就是實現(xiàn)了這個接口構(gòu)建出來的,不妨按照這樣一個模板自己構(gòu)建一個加深理解。

首先看下Lock接口里面都有什么方法:

Modifier and TypeMethodDescription
voidlock()Acquires the lock. 獲取鎖。
voidlockInterruptibly()Acquires the lock unless the current thread is interrupted. 獲取鎖,除非當(dāng)前線程被中斷。
ConditionnewCondition()Returns a new Condition instance that is bound to this Lock instance. 返回一個綁定Condition實例的鎖。
booleantryLock()Acquires the lock only if it is free at the time of invocation. 當(dāng)調(diào)用時鎖為空閑,才能獲取鎖。嘗試獲取鎖。
booleantryLock(long time, TimeUnit unit)Acquires the lock if it is free within the given waiting time and the current thread has not been interrupted.在給定時間內(nèi)處于空閑狀態(tài)且當(dāng)前線程沒有被中斷時,才能獲取鎖。嘗試獲取鎖+超時時間。
voidunlock()Releases the lock. 釋放鎖。

按照上面的模板,我們可以自己構(gòu)建一個鎖工具:

public class MyLock implements Lock {
    private Helper helper = new Helper();
    //按照官方文檔所說,構(gòu)建內(nèi)部幫助類
    private class Helper extends AbstractQueuedSynchronizer {
        //構(gòu)建嘗試獲取鎖的方法
        @Override
        protected boolean tryAcquire(int arg) {
            int state = getState();
            if (0 == state) {
                //利用cas的原理修改state
                if (compareAndSetState(0, arg)) {
                    //設(shè)置當(dāng)前線程擁有資源
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
            }//同一個線程獲取被鎖住的資源時,直接分配給這個線程,實現(xiàn)可重入性
            else if(getExclusiveOwnerThread()==Thread.currentThread()){ //刪除這個else if條件,就會變?yōu)橐粋€不可重入鎖
                setState(getState()+arg);
                return true;
            }
            return false;
        }
        //構(gòu)建嘗試釋放鎖的方法
        @Override
        protected boolean tryRelease(int arg) {
            //arg是傳遞進(jìn)來的state的期望值
            int state = getState() - arg;
            //判斷釋放后狀態(tài)是否為0
            if (0 == state) {
                setExclusiveOwnerThread(null);
                setState(0);
                return true;
            }
            //因為需要修改的線程就是當(dāng)前占有鎖的線程,所以此時直接重置是沒有線程安全問題的,也就是當(dāng)前線程獨占了資源state
            setState(state);
            return false;
        }
        public Condition newConditionObject(){
            return new ConditionObject();
        }
    }
    //加鎖方法
    @Override
    public void lock() {
        helper.acquire(1); //AbstractQueuedSynchronizer原生方法
    }
    //鎖中斷
    @Override
    public void lockInterruptibly() throws InterruptedException {
        helper.acquireInterruptibly(1); //AbstractQueuedSynchronizer原生方法
    }
	//嘗試獲取鎖
    @Override
    public boolean tryLock() {
        return helper.tryAcquire(1); //使用自己實現(xiàn)的tryAcquire()方法
    }
    //嘗試加鎖
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return helper.tryAcquireNanos(1,unit.toNanos(time)); //AbstractQueuedSynchronizer原生方法
    }
	//釋放鎖
    @Override
    public void unlock() {
        helper.tryRelease(1);  //使用自己實現(xiàn)的tryRelease()方法
    }
	//條件
    @Override
    public Condition newCondition() {
        return helper.newConditionObject(); //AbstractQueuedSynchronizer原生方法
    }
}

總結(jié)

到此AQS的內(nèi)容告一段落。這篇博客主要講了AQS的設(shè)計思想,以及操作同步隊列的方式,同時完成了一個簡單的鎖幫助類,希望能夠幫助大家更好的理解鎖這一個同步機(jī)制,以及由AQS架構(gòu)為基礎(chǔ)的各種鎖工具的內(nèi)部原理。

到此這篇關(guān)于Java并發(fā)框架中的AQS詳細(xì)解析的文章就介紹到這了,更多相關(guān)Java的AQS內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java Comparator.comparing比較導(dǎo)致空指針異常的解決

    Java Comparator.comparing比較導(dǎo)致空指針異常的解決

    這篇文章主要介紹了Java Comparator.comparing比較導(dǎo)致空指針異常的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • springboot整合JavaCV實現(xiàn)視頻截取第N幀并保存圖片

    springboot整合JavaCV實現(xiàn)視頻截取第N幀并保存圖片

    這篇文章主要為大家詳細(xì)介紹了springboot如何整合JavaCV實現(xiàn)視頻截取第N幀并保存為圖片,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2023-08-08
  • 關(guān)于java String中intern的深入講解

    關(guān)于java String中intern的深入講解

    這篇文章主要給大家介紹了關(guān)于java String中intern的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 淺談Java 繼承接口同名函數(shù)問題

    淺談Java 繼承接口同名函數(shù)問題

    這篇文章主要介紹了淺談Java 繼承接口同名函數(shù)問題。具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • SpringBoot自定義FailureAnalyzer過程解析

    SpringBoot自定義FailureAnalyzer過程解析

    這篇文章主要介紹了SpringBoot自定義FailureAnalyzer,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-11-11
  • SpringBoot執(zhí)行有返回值的異步任務(wù)問題

    SpringBoot執(zhí)行有返回值的異步任務(wù)問題

    這篇文章主要介紹了SpringBoot執(zhí)行有返回值的異步任務(wù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 關(guān)于如何搭建CAS服務(wù)并將CAS項目導(dǎo)入IDEA

    關(guān)于如何搭建CAS服務(wù)并將CAS項目導(dǎo)入IDEA

    這篇文章主要介紹了關(guān)于如何搭建CAS服務(wù)并將CAS項目導(dǎo)入IDEA的問題,文中提供了詳細(xì)的圖文講解,需要的朋友可以參考下,如果有錯誤的地方還請指正
    2023-03-03
  • Java序列化原理詳解

    Java序列化原理詳解

    這篇文章主要介紹了Java序列化原理詳解,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下
    2022-06-06
  • java生成指定范圍的隨機(jī)日期

    java生成指定范圍的隨機(jī)日期

    這篇文章主要為大家詳細(xì)介紹了java生成指定范圍的隨機(jī)日期,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • 詳解Java?@Documented注解的作用

    詳解Java?@Documented注解的作用

    @Documented和@Deprecated注解長得有點像,@Deprecated是用來標(biāo)注某個類或者方法不建議再繼續(xù)使用,@Documented只能用在注解上,本文將通過示例詳細(xì)說說@Documented注解的作用,需要的可以參考一下
    2022-09-09

最新評論