AbstractQueuedSynchronizer解析 1.8

AbstractQueuedSynchronizer簡稱AQS,即隊列(CLH隊列)同步器。它是構建鎖或者其他同步組件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC併發包的作者(Doug Lea)期望它能夠成爲實現大部分同步需求的基礎。它是JUC併發包中的核心基礎組件
在這裏插入圖片描述

下面先簡單的介紹下AQS中比較重要的方法

//同步狀態,使用者可以對這個狀態進行自定義,一般情況下可以定義爲
//0表示沒有競爭,線程可以直接獲取鎖
//大於0的時候鎖正在被佔用
//對於可重入的獨佔鎖,可以表示爲重入的次數
//對於有憑證數量的共享鎖,可以表示爲憑證數量,獲取一個減去1
private volatile int state;

//使用CAS設置state狀態,該方法能夠保證狀態設置的原子性;
protected final boolean compareAndSetState(int expect, int update)

//獨佔式嘗試獲取同步狀態,需要使用者重寫,返回true表明獲取成功
protected boolean tryAcquire(int arg)

//獨佔式嘗試釋放同步狀態,需要使用者重寫
protected boolean tryRelease(int arg)

//共享式嘗試獲取同步狀態,需要使用者重寫,返回大於等於0表示獲取成功
protected int tryAcquireShared(int arg)

//共享式嘗試釋放同步狀態,需要使用者重寫
protected boolean tryReleaseShared(int arg)

//獨佔式獲取同步狀態,會調用tryAcquire(int arg)嘗試獲取,如果失敗將會將當前線程加入同步隊列
public final void acquire(int arg)

//與acquire(int arg)功能相同,該操作可被中斷,拋出InterruptedException
public final void acquireInterruptibly(int arg)

//獨佔式獲取同步狀態,帶上超時機制,如果nanosTimeout納秒內獲取同步狀態返回true,否則返回false
public final boolean tryAcquireNanos(int arg, long nanosTimeout)

//共享式獲取同步狀態,因爲不是獨佔式的,所以可以有多個線程獲取同步狀態
public final void acquireShared(int arg)

//共享式獲取同步狀態,可中斷
public final void acquireSharedInterruptibly(int arg)

//共享式獲取同步狀態,帶上超時機制
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)

//獨佔式釋放同步狀態,會調用tryRelease(int arg),如果失敗返回false,成功將會喚醒隊列中的第二個節點,第一個是頭節點(一個空節點)
public final boolean release(int arg)

//共享式釋放同步狀態,會調用tryAcquireShared(int arg),如果失敗返回false,成功將會喚醒隊列中的第二個節點
public final boolean releaseShared(int arg)

AQS是一個隊列同步器,它的工作只是在併發的環境下處理線程(同步狀態競爭失敗的線程)入隊、線程掛起、線程喚醒、線程出隊, 而涉及到需要同步獲取狀態的邏輯需要使用者自己去編寫

以獨佔模式爲例,獲取同步狀態的過程

在這裏插入圖片描述
從上圖可以看出同步器的設計是基於模版方法模式的,也就是說,使用者需要繼承同步器並重寫指定的方法,隨後將同步器組合在自定義同步組件的實現中,並調用同步器提供的模版方法,而這些模版方法將會調用使用者重寫的方法

在上圖中模版方法是acquire
用戶需要重寫的方法是tryAcquire

以獨佔模式爲例,釋放同步狀態的過程
在這裏插入圖片描述

下面開始源碼部分,先看下Node類

//隊列節點,存儲等待同步狀態的線程,以及一些狀態
static final class Node {
        //表明該節點中的線程等待的是共享式同步
        static final Node SHARED = new Node();
        
		//表明該節點中的線程等待的是獨佔式同步
        static final Node EXCLUSIVE = null;
        
		//表明該節點被取消,按照順序檢查需要被喚醒的線程的時候會跳過該節點
        static final int CANCELLED =  1;
        
        //因爲是CLH隊列,節點會循環訪問前驅節點的狀態,以確定自身是否可以成功被喚醒,
        //當在前驅節點狀態爲SIGNAL時,表示自己可以放心掛起,等待前驅節點釋放同步狀態的時候將自己喚醒
        //詳情見shouldParkAfterFailedAcquire
        static final int SIGNAL    = -1;
		
		//condition時使用,暫時略
        static final int CONDITION = -2;
        
        //表示下一個acquireShared應該無條件傳播,具體後面分析
        static final int PROPAGATE = -3;
		
		//存儲的線程
		volatile Thread thread;

		//和condition共用,在這裏表示同步的模式 爲null(獨佔)或者 SHARED(共享)
		Node nextWaiter;
}
//隊列頭節點,在這裏頭節點是一個空節點,在線程第一次入隊的時候初始化,
//線程出隊的時候會把自身節點設置成新的頭節點來替代老的頭節點
private transient volatile Node head;

//隊列尾節點
private transient volatile Node tail;

獨佔模式

public final void acquire(int arg) {
		//先調用用戶重寫的tryAcquire,如果失敗則將本線程放入同步隊列中
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    //指明需要用戶繼承重寫
protected boolean tryAcquire(int arg) {
      throw new UnsupportedOperationException();
}

匹配上面的流程圖,這裏先調用tryAcquire,如果失敗再執行入隊操作

看下入隊操作的源碼

//創建節點,傳入節點的類型,這裏是獨佔模式,所以傳入Node.EXCLUSIVE
    private Node addWaiter(Node mode) {
        //調用構造方法,賦值當前線程、節點類型
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //當尾節點不爲空,說明隊列已經初始化過了,那麼直接通過cas將節點加入尾節點
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果尾節點爲null 或者 cas失敗了
        enq(node);
        return node;
    }
 
 //隊列未初始化,或者cas入隊失敗的情況下調用該方法
 private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //如果未初始化隊列,創建head,cas賦值
            if (t == null) { 
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	//如果是之前cas入隊失敗,這裏循環cas 直到成功
            	//需要注意的細節,先賦值prev,再通過cas賦值next,這樣保證了無論cas成功還是失敗,隊列都是完整的
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }   

入隊完成後,獨佔模式下,需要對已入隊的節點進行一系列的處理

//對已入隊的線程進行循環處理(不響應interrupted)
    //1、判斷自己的前驅是否是頭節點,如果是執行tryAcquire,成功則獲取同步狀態出隊,否則2
    //2、修改自己前驅狀態爲SIGNAL(如果前驅狀態爲cancel則略過),成功則將自己掛起等待下次喚醒,否則3
    //3、如果前面都是否,說明在此期間節點發生了變化,那麼進入循環進行下一輪判斷
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //獲取前驅節點
                final Node p = node.predecessor();
                //如果前驅是頭節點,說明這個時候有可能同步狀態已經被釋放了,嘗試一次獲取同步狀態
                if (p == head && tryAcquire(arg)) {
                    //如果成功了,把自己設置成頭節點
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //修改自己前驅狀態爲SIGNAL,處理cancel節點,如果成功了
                //執行parkAndCheckInterrupt,將自己掛起,等待喚醒
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //如果parkAndCheckInterrupt也是true,說明該線程是通過中斷喚醒的,那麼保存中斷標示
                    interrupted = true;
            }
        } finally {
            //如果失敗,比如node.predecessor會拋出NullPointerException
            if (failed)
                //將節點設置成CANCELLED
                cancelAcquire(node);
        }
    }

//修改前驅狀態爲Node.SIGNAL,確保自己被掛起後,前驅能夠叫醒自己
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //如果已經是SIGNAL,則直接返回
        if (ws == Node.SIGNAL)
            return true;
            //如果>0,說明是cancel,那麼這些節點將會被剔除出隊列
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //如果前驅狀態不爲SIGNAL,則進行cas修改
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //如果前驅狀態不爲SIGNAL,則返回進入下一個循環
        return false;
    }
	
	//需要注意的是park是響應中斷的,這個時候需要將中斷標示保存下來返回
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

	//對節點執行取消操作
    private void cancelAcquire(Node node) {
        if (node == null)
            return;

        node.thread = null;

        //如果前驅節點也是cancel,剔除出隊列
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;
        Node predNext = pred.next;

        node.waitStatus = Node.CANCELLED;

        //如果是尾節點則通過cas將自身剔除出隊列
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            
            //如果前驅節點不爲頭節點 而且狀態爲signal 或者可以被cas修改成signal,後繼節點不爲cancel
            //則嘗試將前驅節點直接關聯後繼節點,將自身剔除出隊列
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                //如果修改signal失敗,或者爲頭節點,則喚醒後繼節點
                //這個時候喚醒對後繼節點會執行shouldParkAfterFailedAcquire
                //來剔除cancel節點和設置前驅節點狀態爲SIGNAL
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }
	

到這裏acquire部分就基本說完了,下面來看下獨佔模式下釋放同步狀態

public final boolean release(int arg) {
		//這裏tryRelease也是需要使用者自己重寫對
        if (tryRelease(arg)) {
            Node h = head;
            //如果頭節點狀態爲0,說明後面沒有節點了,否則喚醒後繼節點
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }


    //喚醒node的後繼節點
    private void unparkSuccessor(Node node) {
        
        int ws = node.waitStatus;
        if (ws < 0)
            //先要修改節點狀態,正常情況下這裏ws狀態應該是SIGNAL,標示後繼節點需要喚醒
            //而這裏已經在進行喚醒操作,所以修改成0
            compareAndSetWaitStatus(node, ws, 0);
            
        Node s = node.next;
        //當遇到後繼節點爲空或者爲cancel的情況下
        if (s == null || s.waitStatus > 0) {
            s = null;
            //從tail開始循環往前找最前一個狀態不爲cancel的節點
            //至於爲什麼不從前往後找,因爲addWaiter中是先設置node.prev = pred;再設置pred.next = node;
            //所以從前往後找,也許找不到這個節點
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //將後繼節點喚醒,這個時候被喚醒的線程開始繼續走下一個循環,詳細看acquireQueued
        if (s != null)
            LockSupport.unpark(s.thread);
    }

關於acquireInterruptibly, 與acquire的區別是,前者是響應中斷,會拋出InterruptedException

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

//和acquireQueued邏輯差不多
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //唯一的區別在這裏,這裏檢測到中斷就會直接拋錯
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

關於tryAcquireNanos,可以理解成帶上超時機制的acquire,而且響應中斷

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

//大致邏輯還是差不多的
private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        //先計算超時的時間界限,單位納秒
        final long deadline = System.nanoTime() + nanosTimeout;
        //添加節點
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                //前面嘗試獲取同步狀態失敗後,開始計算剩餘多久超時
                nanosTimeout = deadline - System.nanoTime();
                //如果時間小於0,說明已經超時了 直接退出
                if (nanosTimeout <= 0L)
                    return false;
                //如果剩餘時間大於1000納秒就用超時機制的掛起
                //如果剩餘時間小於1000,就直接進入自旋模式,進入下一個循環繼續判斷
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                //響應中斷
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            //如果超時了,那麼該節點就變成cancel了
            if (failed)
                cancelAcquire(node);
        }
    }

這裏基本上將獨佔模式的獲取同步狀態、釋放同步狀態講完了,接下來看看共享模式部分
先來看看共享式獲取同步狀態

//和獨佔模式的區別在於,這裏tryAcquireShared返回的不是boolean值了,小於0被認爲同步失敗
public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

//邏輯部分和獨佔模式一樣
    private void doAcquireShared(int arg) {
    	//添加節點模式爲SHARED
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //區別在與這句 設置頭部以及擴散
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

	//這個方法做了倆件事
    //1、設置傳入的節點爲頭節點
    //2、查看頭節點的狀態如果是signal或者PROPAGATE,則繼續喚醒下一個節點
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        // 雖然是共享鎖,但是爲了避免不必要的喚醒,
        // 只有當頭節點是signal或者PROPAGATE狀態時才喚醒下一個節點
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //判斷下一個節點是否是共享節點
            if (s == null || s.isShared())
                //喚醒下一個節點
                doReleaseShared();
        }
    }

共享模式允許多個線程同時獲取同步狀態,所以在隊列中等待的線程在被喚醒並且成功獲取同步狀態的時候,會換新下一個節點, 這就是PROPAGATE的含義

最後來看下doReleaseShared部分

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //當狀態爲SIGNAL時,表示可以喚醒下一個節點
                if (ws == Node.SIGNAL) {
                    //這裏通過cas設置爲0是爲了防止多個線程同時執行了unparkSuccessor
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;  
                    //          
                    unparkSuccessor(h);
                }
                //如果發現已經有線程將狀態設置爲0了,則設置成PROPAGATE
                //如果設置不成功則循環繼續設置,也就是說這裏頭節點的狀態最終只會是SIGNAL或者是PROPAGATE
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            //當發現頭節點沒有變過,就可以安全的退出了
            if (h == head)                   // loop if head changed
                break;
        }
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章