日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當前位置:首頁 > > 架構師社區(qū)
[導讀]AQS簡介 AQS(AbstractQueuedSynchronizer)為「抽象隊列同步器」,簡單的說「AQS就是一個抽象類」,抽象類就是AbstractQueuedSynchronizer,沒有實現(xiàn)任何的接口,「僅僅定義了同步狀態(tài)(state)的獲取和釋放的方法」。 它提供了一個「FIFO隊列」,多線程競爭


AQS簡介

AQS(AbstractQueuedSynchronizer)為「抽象隊列同步器」,簡單的說「AQS就是一個抽象類」,抽象類就是AbstractQueuedSynchronizer,沒有實現(xiàn)任何的接口,「僅僅定義了同步狀態(tài)(state)的獲取和釋放的方法」。

它提供了一個「FIFO隊列」,多線程競爭資源的時候,沒有競爭到的線程就會進入隊列中進行等待,并且「定義了一套多線程訪問共享資源的同步框架」。

在AQS中的鎖類型有兩種:分別是「Exclusive(獨占鎖)「和」Share(共享鎖)」。

「獨占鎖」就是「每次都只有一個線程運行」,如ReentrantLock。

「共享鎖」就是「同時可以多個線程運行」,如Semaphore、CountDownLatch、ReentrantReadWriteLock。

AQS源碼分析

在AQS的源碼可以看到對于「state共享變量」,使用「volatile關鍵字」進行修飾,從而「保證了可見性」。從上面的源碼中可以看出,對于state的修改操作提供了setState和compareAndSetState,「那么為什么要提供這兩個對state的修改呢?」

因為compareAndSetState方法「通常使用在獲取到鎖之前」,當前線程不是鎖持有者,對于state的修改可能存在線程安全問題,所以「需要保證對state修改的原子性操作」。

setState方法通常用于當前正持有鎖的線程對「state共享變量」進行修改,因為不存在競爭,是線程安全的,所以沒必要使用CAS操作。

分析了AQS的源碼的實現(xiàn),接下來我們看看AQS的實現(xiàn)的原理。這里AQS的實現(xiàn)源碼和理論都會比較簡單,因為還沒有涉及到具體的實現(xiàn)類。

AQS實現(xiàn)原理

上面說到AQS中維護了一個「FIFO隊列」,并且「該隊列是一個雙向鏈表」,鏈表中的每一個節(jié)點為「Node節(jié)點」,「Node類是AbstractQueuedSynchronizer中的一個內部類」。

讓我們來看看AQS中Node內部類的源碼,這樣有助于我們能夠對AQS的內部的實現(xiàn)更加的清晰:

static final class Node {

        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;

        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

可以看到上面的Node類比較簡單,只是對于每個Node節(jié)點擁有的屬性進行維護,在Node內部類中最重要的基本構成就是這幾個:

volatile Node prev;
    volatile Node next;
    volatile Thread thread;

「根據(jù)源碼的分析和線程的競爭共享資源的原理」,關于AQS的實現(xiàn)原理,我這里畫了一張圖:在FIFO隊列中,「頭節(jié)點占有鎖」,也就是頭節(jié)點才是鎖的持有者,尾指針指向隊列的最后一個等待線程節(jié)點,除了頭節(jié)點和尾節(jié)點,節(jié)點之間都有「前驅指針」「后繼指針」

在AQS中維護了一個「共享變量state」,標識當前的資源是否被線程持有,多線程競爭的時候,會去判斷state是否為0,嘗試的去把state修改為1

分析了AQS的源碼的實現(xiàn)和原理實現(xiàn),但是AQS里面具體是沒有做同步的具體實現(xiàn),如果要什么了解AQS的具體的實現(xiàn)原理,要需要看AQS的具體實現(xiàn)類,這邊就以ReentrantLock為例。

ReentrantLock實現(xiàn)原理

如果多線程在競爭共享資源時,「競爭失敗的線程就會添加入FIFO隊列的尾部」。

ReentrantLock的的具體實現(xiàn)中,這邊以在ReentrantLock的非公平鎖的實現(xiàn)為例,因為公平鎖的實現(xiàn),之前已經寫過一篇文章分析過了。

我們來看看新添加節(jié)點的源碼寫的實現(xiàn)邏輯:當競爭鎖資源的線程失敗后直接進入acquire(1)方法,從源碼中可以看出,acquire(1)的實現(xiàn)主要有這三步的邏輯:

  1. tryAcquire(arg):嘗試再次獲取鎖。
  2. addWaiter(Node.EXCLUSIVE):若是獲取鎖失敗,就會將當前線程組裝成一個Node節(jié)點,進行入隊操作。
  3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg)):acquireQueued方法以addWaiter返回的頭節(jié)點作為參數(shù),內部實現(xiàn)進行鎖自旋,以及判斷是否應該執(zhí)行線程掛起。

下面我們再來看看tryAcquire(arg)的源碼,從上面的看一看出arg的值為1,具體的實現(xiàn)源碼如下:從源碼的分析中可以看出,tryAcquire(arg)的實現(xiàn)也就是判斷state的值是否已經被釋放,「若釋放則當前線程就會CAS操作將state設置為1,若是沒有釋放,就會判斷是否可以進行鎖的重入」。

分析完tryAcquire(arg)的實現(xiàn),來看看addWaiter:對于新加入的線程添加到雙向鏈表中使用尾插法。當線程加入隊列中,主要進行這幾步操作「新加入的節(jié)點prev指針指向原來的tail節(jié)點,原來的tail節(jié)點的next指針指向新加入的節(jié)點」,這個也就是常見的「雙向鏈表尾插法」的操作。

「最后把tail指向新加入的節(jié)點」,如此一來就完成了新加入節(jié)點的入隊操作,接下來我們接著分析源碼。

當然這里的前提是「隊列中不為空」,若是為空的話,不會走上面的邏輯,而是走enq(node),進行初始化節(jié)點,我們來看看enq(node)操作:執(zhí)行完上面的入對操作后,接著執(zhí)行acquireQueued方法,來看看它的具體實現(xiàn)源碼:從上上面的源碼中可以看出,涉及到「頭節(jié)點head的出隊」操作,并且將「當前線程的node節(jié)點晉升為head節(jié)點」。

因為只有「頭節(jié)點才是鎖的持有者」,所以對于head節(jié)點的出隊操作,head的指向會隨時改變:會把「原來的頭節(jié)點進行出隊操作,也就是把原來的頭節(jié)點next指針指向null,原來第二節(jié)點的prev指針指向null」。

最后把head指針指向第二節(jié)點,當然thread2同時還會修改共享狀態(tài)變量state的值,如此一來就完成了鎖的釋放。

當釋放完鎖之后,就會執(zhí)行shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()判斷當前的線程是否應該被掛起,我們來看看它的源碼實現(xiàn):在shouldParkAfterFailedAcquire中的實現(xiàn),當前驅節(jié)點的狀態(tài)量waitStatus為SIGNAL的時候,就會掛起。通過上面的分析對于AQS的實現(xiàn)基本有比較清晰的認識,主要是對實現(xiàn)類ReentrantLock的實現(xiàn)原理進行深入的分析,并且是基于「非公平鎖」「獨占鎖」的實現(xiàn)。

在AQS的底層維護了一個「FIFO隊列」,多線程競爭共享資源的時候,「失敗的線程會被添加入隊列中」「非公平鎖」實現(xiàn)中,新加入的線程節(jié)點會「自旋」嘗試的獲取鎖。

分析完AQS我們來分析CAS,「那么什么是CAS呢?」

CAS簡介

在分析ReentrantLock的具體實現(xiàn)的源碼中,可以看出所有涉及設置共享變量的操作,都會指向CAS操作,保證原子性操作。

CAS(compare and swap)原語理解就是比較并交換的意思,「CAS是一種樂觀鎖的實現(xiàn)」。

在CAS的算法實現(xiàn)中有三個值:「更新的變量」、「舊的值」、「新值」。在修改共享資源時候,會與原值進行比較,若是等于原值,就修改為新值。

于是在這里的算法實現(xiàn)下,即使不加鎖,也能保證數(shù)據(jù)的可見性,即使的發(fā)現(xiàn)數(shù)據(jù)是否被更改,若是數(shù)據(jù)已經被更新則寫操作失敗。

但是CAS也會引發(fā)ABA的問題,「什么是ABA問題呢?」 不慌請聽我詳細道來

ABA問題

ABA問題就是假如有兩個線程,同一時間讀取一個共享變量state=1,此時兩個線程都已經將state的副本賦值到自己的工作內存中。

當線程一對state修改state=state+1,并且寫入到主存中,然后線程一又對state=state-1寫入到主存,此時主存的state是變化了兩次,只不過又變回了原來的值。

那么此時線程二修改state的時候就會修改成功,這就是ABA問題。對于「ABA問題的解決方案就是加版本號(version)」,每次進行比較的時候,也會比較版本號。

因為版本版是只增不減,比如以時間作為版本號,每一時刻的時間都不一樣,這樣就能避免ABA的問題。

CAS性能分析

相對于「synchronized的阻塞算法」的實現(xiàn),「CAS采用的是樂觀鎖的非阻塞算法」的實現(xiàn),一般CPU在進行線程的上下文切換的時間比執(zhí)行CPU的指令集的時間長,所以CAS操作在性能上也有了很大的提升。

但是所有的算法都是沒有最完美的,在執(zhí)行CAS的操作中,沒有更新成功的就會自旋,這樣也會消耗CPU的資源,對于CPU來說是不友好的。

特別推薦一個分享架構+算法的優(yōu)質內容,還沒關注的小伙伴,可以長按關注一下:

深入剖析AQS和CAS,看了都說好

長按訂閱更多精彩▼

深入剖析AQS和CAS,看了都說好

如有收獲,點個在看,誠摯感謝

免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
關閉