并發(fā)編程之AQS探秘

在上一篇文章并發(fā)編程之生產(chǎn)者消費者模型四種實現(xiàn)中等脂,我們引入了并發(fā)編程的話題。而并發(fā)編程的目的围橡,無非就是合理利用過剩的CPU資源梗搅,來達到提高程序性能刃榨,從而創(chuàng)造更多業(yè)務價值的目的社裆。而要合理的完成并發(fā)編程的工作拙绊,其中繞不開的兩個點就是:鎖的運用(jdk自帶鎖、基于數(shù)據(jù)結構的鎖)泳秀,以及并發(fā)工具類的運用(并發(fā)集合标沪、線程池)。而并發(fā)工具類以及基于數(shù)據(jù)結構的鎖又離不開一個重要的框架AbstractQueuedSynchronizer(AQS)嗜傅。本文將會一探AQS的秘密金句。本文主要包含以下部分:

  1. 前言
  2. AQS 基礎
    2.1 AQS 定義
    2.2 AQS 接口
    2.3 利用AQS實現(xiàn)自定義獨占鎖
  3. AQS 原理
    3.1 同步隊列
    3.1.1 Node
    3.1.2 同步隊列的執(zhí)行流程
    3.2 獨占鎖的獲取和釋放
    3.2.1 獨占鎖的獲取
    3.2.2 獨占鎖的釋放
  4. 由AQS看JUC
  5. 總結

1. 前言

談到并發(fā)編程就不得不提一個詞,按照鎖的實現(xiàn)(而非定義)來講吕嘀,可以分為語言級別的鎖(synchronized),以及語義級別的鎖(基于特殊數(shù)據(jù)結構實現(xiàn)的鎖)违寞。語義級別的鎖常見的如ReentrantLock、ReadWriteLock币他。這兩種鎖都有一個共同的核心AbstractQueuedSynchronized(AQS)坞靶。同時我們常用的并發(fā)工具類 如Condition(基于數(shù)據(jù)結構實現(xiàn)等待憔狞、通知模式蝴悉。替代ObjectMonitor的語義)、CountDownLatch(同步計數(shù)器)瘾敢、CyclicBarrier(回環(huán)柵欄)拍冠、Semaphore(信號量)尿这、ThreadPoolExecutor(線程池)這些都是基于AQS實現(xiàn)或者有用到AQS∏於牛可以看到AQS幾乎占到了并發(fā)編程的半壁江山射众,是當之無愧的大哥級人物

我們都知道synchronized是基于ObjectMonitor晃财,最終通過操作系統(tǒng)層面的Mutex(互斥鎖)來實現(xiàn)線程同步的叨橱。那么與之對應的AQS呢?思考如下問題:

  • AQS如何實現(xiàn)獨占鎖的獲取和釋放呢断盛?跟Mointor有沒有關系呢罗洗?
  • AQS如何實現(xiàn)共享鎖的獲取和釋放呢?其處理模式和獨占有什么區(qū)別呢钢猛?
    帶著這些問題伙菜,我們來一探AQS的究竟。

2. AQS 基礎

2.1 AQS的定義

  • AQS即AbstractQueuedSynchronizer命迈,即抽象隊列同步器又稱同步器贩绕。是用來構建鎖或者其它同步組件的基礎框架\color{DarkOrchid}{它使用了一個int成員變量來表示同步狀態(tài)壶愤,同時內部維護了一個FIFO(先進先出)隊列淑倾,來完成獲取資源線程的排隊工作。}
  • AQS提供了一系列的模板方法來構建獲得資源和釋放資源的框架公你,以及預設的通用操作(如獲取資源失失敗時的排隊操作踊淳、釋放資源后喚醒等待的后續(xù)線程等)。使用的時候陕靠,子類需要繼承AbstractQueuedSynchronized迂尝,并按需重寫抽象方法即可

簡單來說AQS是JUC包的作者剪芥,并發(fā)編程大師Doug Lea 為開發(fā)者提供的實現(xiàn)同步需求的框架垄开,我們可以通過使用它來簡便的實現(xiàn)自定義同步組件,而無需實現(xiàn)復雜的隊列入隊出隊及喚醒等復雜操作税肪。

2.2 AQS接口示例

AQS是通過模板方法模式來實現(xiàn)的溉躲,而模板方法模式的實現(xiàn)原理為:子類通過繼承模板類,并實現(xiàn)模板類的抽象方法益兄。而當調用模板類的模板方法時锻梳,會調用子類實現(xiàn)的抽象方法。那么這里就會涉及到兩種方法:需要子類去重寫的抽象方法净捅、模板方法疑枯。那么AQS中同樣也存在這兩類方法。

需要子類去重寫的抽象方法

名稱 作用
protected boolean tryAcquire(int arg) 獨占式的獲取同步狀態(tài)
protected boolean tryRelease(int arg) 獨占式的釋放同步狀態(tài)
protected int tryAcquireShared(int arg) 共享式的獲取同步狀態(tài)
protected boolean tryReleaseShared(int arg) 共享式的釋放同步狀態(tài)
protected boolean isHeldExclusively() 是否線程獨占

AQS提供的模板方法

名稱 作用 說明
public final void acquire(int arg) 獨占式的獲取同步狀態(tài) 獲取成功方法返回蛔六,否則進入隊列排隊荆永。是否獲取成功由重寫的tryAcquire(int arg)方法決定废亭。
public final void acquireShared 共享式的獲取同步狀態(tài) 獲取成功方法返回,否則進入隊列排隊具钥。是否獲取成功豆村,最多有幾個線程能獲取共享同步狀態(tài)由tryAcquireShared(int arg)決定。
public final boolean release(int arg) 獨占式的釋放同步狀態(tài) 釋放成功后骂删,會喚醒等待隊列的Header節(jié)點持有的線程掌动。
protected boolean tryReleaseShared(int arg) 共享式的釋放同步狀態(tài) 同樣是喚醒等待的header節(jié)點。

篇幅所限宁玫,只列出了部分方法坏匪。

2.3 利用AQS實現(xiàn)自定義獨占鎖

嘗試用AQS實現(xiàn)一個獨占鎖

//自定義獨占鎖,根據(jù)state狀態(tài)判斷獨占狀態(tài) 0:未加鎖  1:已加鎖
public class Mutex implements Lock {
    //1. 自自定義內部類繼承AbstractQueuedSynchronizer
    private static class Syn extends AbstractQueuedSynchronizer {
        //2. 按需重寫抽象方法
        //是否線程獨占
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
        //2. 按需抽象方法
        //獨占式的獲取鎖
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        //2. 按需抽象方法
        //獨占式的釋放鎖
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;

        }
        //2. 按需抽象方法
        //返回AQS內部類ConditionObject
        Condition newCondition() {
            return new ConditionObject();
        }
    }
    //3.將內部類組合到同步組件中撬统,以便后面使用內部類從AQS繼承的模板方法
    private Syn syn = new Syn();
    //4. 使用AQS提供的模板方法
    //獨占式的加鎖
    @Override
    public void lock() {
        syn.acquire(1);
    }
    //4. 使用AQS提供的模板方法
    //支持中斷的加鎖
    @Override
    public void lockInterruptibly() throws InterruptedException {
        syn.acquireInterruptibly(1);
    }
    //4. 使用AQS提供的模板方法
    //非阻塞的情況下拿鎖
    @Override
    public boolean tryLock() {
        return syn.tryAcquire(1);
    }
    //4. 使用AQS提供的模板方法
    //非阻塞的情況下拿鎖适滓,支持超時時間
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return syn.tryAcquireNanos(1, unit.toNanos(time));
    }
    //4. 使用AQS提供的模板方法
    //獨占式的釋放鎖
    @Override
    public void unlock() {
        syn.release(1);
    }
    //4. 使用AQS提供的模板方法
    //獲取與鎖相關聯(lián)的Condition
    @Override
    public Condition newCondition() {
        return syn.newCondition();
    }

    public static void main(String[] args) {
        Lock mutex = new Mutex();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "嘗試獲得鎖[" + mutex + "]");
            mutex.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "持有了鎖[" + mutex + "]"+"@ time["+ LocalDateTime.now().toString()+"]");
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                mutex.unlock();
            }
        }, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "嘗試獲得鎖[" + mutex + "]");
            mutex.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "持有了鎖[" + mutex + "]"+"@ time["+ LocalDateTime.now().toString()+"]");
            } finally {
                mutex.unlock();
            }
        }, "t2").start();

    }
}

輸出結果

t1嘗試獲得鎖[com.moxieliunian.lock.AQSLock.Mutex@5be6cb40]
t1持有了鎖[com.moxieliunian.lock.AQSLock.Mutex@5be6cb40]@ time[2020-12-01T14:49:28.161]
t2嘗試獲得鎖[com.moxieliunian.lock.AQSLock.Mutex@5be6cb40]
t2持有了鎖[com.moxieliunian.lock.AQSLock.Mutex@5be6cb40]@ time[2020-12-01T14:49:38.161]

可以看到t2線程在t1線程釋放鎖后才能獲得鎖,間隔時間剛好是我們設置的t1持有鎖時間10s恋追。

從上面的例子中凭迹,我們看到了使用AQS自定義同步組件的步驟:

  1. 自定義內部類繼承AbstractQueuedSynchronizer。
  2. 按需重寫抽象方法苦囱。
  3. 將內部類組合到同步組件中嗅绸,以便后面使用內部類從AQS繼承的模板方法。
  4. 使用AQS提供的模板方法撕彤。

我們只需要簡單的實現(xiàn)抽象方法鱼鸠,無需復雜的操作,即可實現(xiàn)自定義同步組件的邏輯羹铅。是不是很神奇蚀狰?下面讓我們一探究竟吧。


3. AQS原理

上面我們說到AQS的關鍵在于維持了一個FIFO的隊列职员,用于獲取同步資源失敗時入隊等待麻蹋,以及持有鎖的線程釋放同步資源時出隊喚醒。所以為了搞清楚AQS的工作原理焊切,先了解其內部的同步隊列是很有必要的扮授。因此下面會按照如下順序介紹:同步隊列、獨占鎖的獲取和釋放专肪。

在介紹源碼之前刹勃,我們先要重申下以下幾個概念:

  • 同步器:即AQS本身。其持有同步隊列的首節(jié)點和尾節(jié)點引用嚎尤。
  • 同步隊列:即由Node節(jié)點組成的雙向隊列荔仁,該隊列支持FIFO,每個Node存在指向前驅和后繼節(jié)點的引用。

3.1 同步隊列

3.1.1 Node

整個同步隊列對應的類為源碼-AbstractQueuedSynchronizer.node

static final class Node {
        //共享模式
        static final Node SHARED = new Node();
        //獨占模式
        static final Node EXCLUSIVE = null;
        //取消狀態(tài)咕晋,同步隊列等待的線程超時或者被中斷后變成取消狀態(tài)
        static final int CANCELLED =  1;
        //等待通知狀態(tài)。如果獲得了同步資源的線程釋放了同步資源收奔,將通知后續(xù)節(jié)點掌呜,后續(xù)節(jié)點接觸阻塞,繼續(xù)運行
        static final int SIGNAL    = -1;
        //節(jié)點在等待隊列中坪哄,處于Condition.await()狀態(tài)质蕉,需等待Condition.signal()方法被調用后,才能從等待隊列進入到同步隊列中
        static final int CONDITION = -2;
        //下一次共享式同步狀態(tài)將會被無條件的傳播下去
        static final int PROPAGATE = -3;

        //等待狀態(tài)翩肌,為以上狀態(tài)的一種
        volatile int waitStatus;

        //前驅節(jié)點
        volatile Node prev;
        //后置節(jié)點
        volatile Node next;
        //當前嘗試獲取同步狀態(tài)的線程
        volatile Thread thread;
        //等待隊列的后續(xù)節(jié)點
        Node nextWaiter;

        //是否共享模式
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        //返回前驅節(jié)點
        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
        }
        //addWaiter時使用的構造方法模暗,設置后繼節(jié)點和當前線程
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        //配合Condition時使用的構造方法,設置等待狀態(tài)和當前線程
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

從上面的源碼中念祭,我們可以看到幾個關鍵點

  • 每個Node都維護了一個volatile int 類型的變量waitStatus來標識同步的狀態(tài)兑宇。
  • 每個Node都持有前驅節(jié)點的引用 prev,同樣被volatile修飾粱坤。
  • 每個Node都持有后繼節(jié)點的引用 next隶糕,同樣被volatile修飾。
  • 每個節(jié)點都持有當前線程的引用 thread站玄,V同樣被volatile修飾枚驻。
    我們都知道volatile是為了保持變量修改后對線程立馬可見,那么對volatile變量的修改又是如何進行的呢株旷?我們以設置尾節(jié)點為例來看
    /**
     * CAS tail field. Used only by enq.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

我們發(fā)現(xiàn)了一個有意思的東西unsafe類再登。明顯更新Node的屬性絕大部分都是通過CAS來進行操作的。

3.1.2 同步隊列的執(zhí)行流程

Node是構成同步隊列的基礎晾剖。那么同步器自身又是如何與Node關聯(lián)的呢锉矢?
同步器(AQS)持有兩個Node引用,分別是同步隊列的頭節(jié)點head齿尽,以及同步隊列的尾節(jié)點tail沈撞。

    //頭節(jié)點
    private transient volatile Node head;
    //尾節(jié)點
    private transient volatile Node tail;

同步器依賴同步隊列來完成同步狀態(tài)的管理,主要涉及到兩方面:

  • 當前線程獲取同步狀態(tài)失敗時雕什,將其構造成Node缠俺,加入同步隊列的尾部,阻塞當前線程等待被喚醒贷岸。
  • 當前線程釋放同步狀態(tài)時壹士,喚醒同步隊列的header節(jié)點持有的線程,并使其再次嘗試獲得同步狀態(tài)偿警。

以上兩個流程分別對應的是同步隊列的兩個動作:獲取資源失敗時的入隊(尾部插入添加節(jié)點)躏救,成功釋放資源后的出隊(頭部移除節(jié)點)。我們分別按照同步隊列的基本結構、同步隊列的入隊盒使、同步隊列的出隊來介紹大致流程崩掘。

  1. 同步隊列的基本結構
    同步隊列為一個雙向隊列,每個Node節(jié)點分別持有前驅節(jié)點和后繼節(jié)點的引用少办。同時同步器持有同步隊列的header節(jié)點和tail節(jié)點的引用苞慢。
    同步隊列基本結構.png

    值得注意的是頭部節(jié)點的prev屬性為空(首節(jié)點不存在前驅節(jié)點),尾部節(jié)點的next屬性為空(尾部節(jié)點不存在后繼節(jié)點)
  2. 獲取資源失敗時的入隊操作
    上面我們說過英妓,在獲取資源失敗時會將當前線程和waitStatus構建成Node挽放,加入同步隊列的尾部進行入隊操作。即1.構建Node并將其加入隊列尾部蔓纠。2.將同步器的tail屬性指向新的Node辑畦。
    同步隊列入隊操作.png

    值得注意的是,之所入隊時向尾部插入腿倚,是為了FIFO的原則纯出,尾部是最新加入的節(jié)點
  3. 成功釋放資源后的出隊
    當獲取同步資源成功的線程釋放同步資源時,會喚醒后繼節(jié)點敷燎,后繼節(jié)點在嘗試獲取同步資源潦刃,獲取成功時會將自己設置成頭節(jié)點。對應同步隊列的操作即1.將后繼節(jié)點設置為新的header懈叹。2.斷開原來header的next指向(方便GC)乖杠。
    同步隊列出隊操作.png

整個同步隊列的執(zhí)行流程有個有意思的地方:添加尾部節(jié)點時為了保證線程安全使用了CAS更新,但是在移除節(jié)點的設置頭節(jié)點的步驟中澄成,并未做線程安全的處理胧洒。這是為什么呢?
因為同時有多個線程做加入隊列尾部的操作墨状,所以在入隊的時候要通過CAS來保證線程安全卫漫。而設置頭節(jié)點的前提時成功獲取到了鎖,而只能有一個線程能獲得鎖肾砂,故設置頭節(jié)點的時候不需要額外線程安全處理列赎。

3.2 獨占鎖的獲取和釋放

其實說清楚同步隊列的工作流程后,整個同步器的工作流程也已經(jīng)了解大半了镐确。下面我們分別以獨占鎖的獲取和獨占鎖的釋放為例來看看具體的源碼包吝。

3.2.1 獨占鎖的獲取

    public final void acquire(int arg) {
        //獲取同步狀態(tài)
        if (!tryAcquire(arg) &&
            //獲取失敗時,構造Node源葫,并將該Node加入到同步隊列的尾部
            //使同步隊列的節(jié)點以死循環(huán)的方式獲取同步狀態(tài)诗越,獲取不到則阻塞,直到被喚醒或者打斷
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

該方法主要包含了以下幾個關鍵步驟:

  • 獲取同步狀態(tài)息堂。
  • 如果獲取失敗則構造Node嚷狞,并將其加入同步隊列尾部块促。
  • 使該節(jié)點以死循環(huán)的方式獲取同步狀態(tài),獲取失敗則阻塞床未,直到被頭部節(jié)點喚醒或者被中斷竭翠。

下面分別來說明以上過程

  1. 獲取同步狀態(tài)
    獲取同步狀態(tài)是通過tryAcquire(arg)方法來實現(xiàn)的,該方法為抽象方法薇搁,需要子類去實現(xiàn)斋扰。
   protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

該方法要保證線程安全的獲取同步狀態(tài)

  1. 獲取失敗時構造Node,并將其加入同步隊列尾部
    這步操作是通過addWaiter(Node.EXCLUSIVE)實現(xiàn)的
    private Node addWaiter(Node mode) {
        //根據(jù)當前Thread和Node.EXCLUSIVE(獨占模式的Node常量)來構造當前節(jié)點
        Node node = new Node(Thread.currentThread(), mode);
        // 快速設置尾部只酥,只有同步隊列已有數(shù)據(jù)后才能成功
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //將Node進行入隊,未初始的情況下先進行初始化head和tail呀狼,并通過死循環(huán)的方式CAS設置tail裂允,直到成功
        enq(node);
        return node;
    }

這里主要包括兩個關鍵步驟:

  • 以Thread.currentThread()和Node.EXCLUSIVE作為參數(shù)構建Node
  • 將Node進行入隊,未初始的情況下先進行初始化head和tail哥艇,并通過死循環(huán)的方式CAS設置tail绝编,直到成功

構建Node的時候會傳入當前線程信息,以便后續(xù)通過Node喚醒指定的線程貌踏。

  private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // tail為空十饥,需要初始化
                //初始化header
                if (compareAndSetHead(new Node()))
                  //將tail臨時指向header
                    tail = head;
            } else {
                node.prev = t;
                //死循環(huán)CAS設置tail為當前Node,直至成功
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

可以看到enq(node主要做了兩件事1. 初始化header祖乳、2. 設置當前node為tail逗堵。入隊的示意圖如下:

addWaiter流程圖.png

值得注意的是只有compareAndSetTail(t, node)返回成功后,才會退出當前循環(huán)眷昆。也就是說通過CAS將并發(fā)添加Node變得串行了。

  1. 以死循環(huán)的方式獲取同步狀態(tài),獲取失敗則阻塞虑啤,直到被頭部節(jié)點喚醒或者被中斷
    這步操作是通過acquireQueued(final Node node, int arg)方法實現(xiàn)的
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //死循環(huán)自旋
            for (;;) {
                //前驅節(jié)點
                final Node p = node.predecessor();
                //前驅節(jié)點為頭節(jié)點拂募,嘗試獲取同步狀態(tài)且成功
                if (p == head && tryAcquire(arg)) {
                    //設置當前節(jié)點為頭節(jié)點
                    setHead(node);
                    //斷開原始頭節(jié)點的next引用
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //根據(jù)waitStatua檢測是否需要掛起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //掛起線程
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

這里主要做了兩件事:

  • 步驟1: 檢查當前節(jié)點的前驅節(jié)點是否為header節(jié)點(即判斷當前節(jié)點是否為成功獲取鎖的Node的后繼節(jié)點),如果是則嘗試獲取帅刊。
  • 步驟2:如果步驟1不滿足(前驅節(jié)點不是heaer節(jié)點纸泡,或者獲取鎖失敗)赖瞒,則根據(jù)狀態(tài)進行掛起女揭。

值得注意的是:上訴步驟1和2是以死循環(huán)的方式運行的,也就是我們常說的節(jié)點自旋的過程栏饮。


節(jié)點的自旋過程.png

我們再梳理下整個獲取獨占鎖的獲取流程:

  • 嘗試獲取同步狀態(tài)田绑。
  • 獲取成功,返回抡爹。
  • 獲取失敗掩驱,則構造Node,并加入同步隊列尾部。
  • 同步隊列進行自旋欧穴。
  • 當前節(jié)點的前驅節(jié)點為頭部節(jié)點且嘗試獲取同步狀態(tài)成功民逼,則退出自旋。
  • 上一步不滿足涮帘,則根據(jù)狀態(tài)進行掛起拼苍。直到被喚醒或者中斷,然后重復上一步的操作调缨。
    獨占鎖的獲取流程.png

3.2.2 獨占鎖的釋放

說完了獨占鎖的獲取疮鲫,再看獨占鎖的釋放要簡單的多。釋放同步狀態(tài)是通過調用模板方法release(int arg)實現(xiàn)的弦叶。

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
              //喚醒頭部節(jié)點的后繼節(jié)點
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

該方法主要包含以下幾個關鍵步驟:

  • 釋放同步資源俊犯。
  • 喚醒被掛起的后繼線程。
  1. 釋放同步資源
    釋放同步狀態(tài)是通過tryRelease(int arg)方法實現(xiàn)的伤哺,該方法同樣為抽象方法燕侠,需要子類去實現(xiàn)。
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
  1. 喚醒被掛起的后繼線程'
 private void unparkSuccessor(Node node) {
    
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        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;
        }
        //喚醒頭部節(jié)點的后繼節(jié)點
        if (s != null)
            LockSupport.unpark(s.thread);
    }

掛起和恢復線程使用到了一個關鍵類立莉,LockSupport.park(Object blocker)绢彤、LockSupport.unpark(Thread thread)

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

4. 由AQS看JUC

上面我們分析了AQS的源碼蜓耻,可以看到兩個關鍵點volatile && CAS茫舶。可以看到AQS做了如下操作

  • 將變量聲明為volatile類型(Node的prev,next AQS的tail和head)
  • 利用CAS更新來實現(xiàn)線程之間的同步
  • 配合volatile的讀寫語義實現(xiàn)線程之間的通信

上面的套路也是整個JUC包的底層實現(xiàn)套路刹淌。JUC包下的內容按照依賴層級可以分為兩部分:處于上層的:Lock奇适、同步器、阻塞隊列芦鳍、線程池嚷往、并發(fā)容器處于下層的:AQS柠衅、原子變量類皮仁。上層類依賴下層類去實現(xiàn),而下層類又依賴于volatile && CAS(unsafe)


5. 總結

上面我們詳細解讀了AQS的源碼菲宴,關鍵部分volatile && CAS && unsafe.park && unsafe.unpark贷祈。篇幅所限,并未介紹共享鎖的獲取與釋放喝峦。感興趣的可以自己下面去研究势誊,與獨占鎖最主要的區(qū)別就在于同時不止一個線程可以獲取鎖。

由于技術水平所限谣蠢,文章難免有不足之處粟耻,歡迎大家指出查近。希望每位讀者都能有新的收獲,我們下一篇文章并發(fā)編程之synchronized的前世今生見....

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末挤忙,一起剝皮案震驚了整個濱河市霜威,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌册烈,老刑警劉巖戈泼,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赏僧,居然都是意外死亡大猛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門淀零,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挽绩,“玉大人,你說我怎么就攤上這事窑滞∏砟粒” “怎么了恢筝?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵哀卫,是天一觀的道長。 經(jīng)常有香客問我撬槽,道長此改,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任侄柔,我火速辦了婚禮共啃,結果婚禮上,老公的妹妹穿的比我還像新娘暂题。我一直安慰自己移剪,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布薪者。 她就那樣靜靜地躺著纵苛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪言津。 梳的紋絲不亂的頭發(fā)上攻人,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音悬槽,去河邊找鬼怀吻。 笑死,一個胖子當著我的面吹牛初婆,可吹牛的內容都是我干的蓬坡。 我是一名探鬼主播猿棉,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼渣窜!你這毒婦竟也來了铺根?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤乔宿,失蹤者是張志新(化名)和其女友劉穎位迂,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體详瑞,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡掂林,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坝橡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泻帮。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖计寇,靈堂內的尸體忽然破棺而出锣杂,到底是詐尸還是另有隱情,我是刑警寧澤番宁,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布元莫,位于F島的核電站,受9級特大地震影響蝶押,放射性物質發(fā)生泄漏踱蠢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一棋电、第九天 我趴在偏房一處隱蔽的房頂上張望茎截。 院中可真熱鬧,春花似錦赶盔、人聲如沸企锌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撕攒。三九已至,卻和暖如春沉眶,著一層夾襖步出監(jiān)牢的瞬間打却,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工谎倔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留柳击,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓片习,卻偏偏與公主長得像捌肴,于是被迫代替她去往敵國和親蹬叭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容