JUC---AbstractQueuedSynchronizer解析(JDK13)

java.util.concurrent包系列文章
JUC—ThreadLocal源碼解析(JDK13)
JUC—ThreadPoolExecutor線程池源碼解析(JDK13)
JUC—各種鎖(JDK13)
JUC—原子類Atomic*.java源碼解析(JDK13)
JUC—CAS源碼解析(JDK13)
JUC—ConcurrentHashMap源碼解析(JDK13)
JUC—CopyOnWriteArrayList源碼解析(JDK13)
JUC—併發隊列源碼解析(JDK13)
JUC—多線程下控制併發流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)


本篇是JUC系列的最後一篇,有請最最神祕的AbstractQueuedSynchronizer—AQS。它是衆多併發工具的基石。是一個用於構建鎖,同步器,線程協作工具類的框架。


一、AQS

AQS是JDK1.5加入的一個基於FIFO等待隊列實現的一個用於實現同步器的基礎框架。
前面幾篇文章展示了ReentrantLock,ReentrantReadWriteLock,Semaphore,CountDownLatch等線程協作類的使用。他們的底層都是基於AQS的。AQS就是一個支持併發的工具類。抽象出了一些支持併發需要的共同屬性和方法。在它基礎之上的各種併發類的實現只要根據自己的需要去編寫自己特有的併發邏輯。
AQS的實現類,很多熟悉的身影
在這裏插入圖片描述

看看代碼

Semaphore
在這裏插入圖片描述

ReentrantLock
在這裏插入圖片描述
它們有個共同點是都有個Sync內部類繼承自AbstractQueuedSynchronizer,擁有了AQS的方法。


二、AQS的三核心

AQS的核心有三

  • 1、併發肯定會有需要競爭的某個資源,那就包括這個資源的同步狀態的原子性管理。state屬性
  • 2、併發肯定會有隊列來放等待的線程。FIFO隊列
  • 3、既然需要線程掛起等待,那肯定需要線程的阻塞與解除阻塞。需要實現類去實現的獲取/釋放方法

結合圖看一下,三大核心
在這裏插入圖片描述

state

/**
 * The synchronization state.
 */
// state是volatile修飾的
private volatile int state;

state的具體含義在不同的實現類中有不同的意義。

  • 在Semaphore中代表剩餘的信號量,一個線程拿走一個信號量state就減一,釋放一個就加一。
  • 在CountDownLatch中代表還要倒數的數量。
  • 在ReentrantLock中代表鎖的佔有情況,有線程獲取到鎖state爲1,沒有任何線程持有鎖,state爲0。如果是可重入進來的鎖,state+1。

對state的操作必須保證線程安全,一共2個方法可以改變state的值

// 這個方法只有在初始化纔會調用
protected final void setState(int newState) {
        state = newState;
}
// CAS保證操作原子性
protected final boolean compareAndSetState(int expect, int update) {
    return STATE.compareAndSet(this, expect, update);
}

FIFO隊列

這個隊列用來存放“等待的線程”,AQS就是排隊管理器。當鎖釋放時,就挑選一個合適的線程來佔有這個釋放的鎖。

在這裏插入圖片描述
head表示現在拿到鎖的現在,正在執行。後面就是等待隊列。

需要協作類去自己實現的獲取/釋放的方法
獲取方法,獲取操作會依賴state變量,經常會阻塞,獲取不到鎖就阻塞。

  • 在Semaphore中獲取方法就是acquire()方法,獲取一個許可證。獲取到了state就減一。如果state是0,那麼就獲取不到,進入等待隊列阻塞。
  • 在CountDownLatch裏面,獲取就是await()方法,作用是“等待,直到倒數結束”,如果state不爲0就一直阻塞。直到其他線程把state減爲0,就可以繼續執行。
  • 在ReentrantLock中,獲取就是lock()方法。state+1。

釋放方法,不會阻塞

  • 在Semaphore中釋放方法就是release()方法,釋放一個許可證。state加一。
  • 在CountDownLatch裏面,釋放就是countDown()方法,state減一。
  • 在ReentrantLock中,獲取就是unlock()方法。state-1。

三、從源碼看AQS的用法

1、看看CountDownLatch是怎麼使用AQS的

源碼分4部分看

初始化構造方法

// 構造方法,count就是要倒數的數量
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    // 調用了Sync的構造方法
    this.sync = new Sync(count);
}
// Sync的構造方法,把count賦值給了state變量
Sync(int count) {
    setState(count);
}

getCount方法

// 獲取當前的count
public long getCount() {
  return sync.getCount();
}

等待相關方法

// await等待方法
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 如果小於0,說明還沒倒數結束,放入等待隊列,等待倒數完畢
    if (tryAcquireShared(arg) < 0)
    	// 把當前線程放入等待隊列並該阻塞
        doAcquireSharedInterruptibly(arg);
}
// 判斷state是否爲0,爲0說明倒數結束了,返回1,不需要去等待了
// state>0說明還沒倒數結束,返回-1
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

// 把當前線程放入等待隊列並阻塞的方法
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    // 把當前線程包裝成Node節點,就是上圖阻塞隊列中的Node
    final Node node = addWaiter(Node.SHARED);
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())// 阻塞線程
                throw new InterruptedException();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        throw t;
    }
}
// 阻塞線程的方法
private final boolean parkAndCheckInterrupt() {
	// 掛起線程的方法,LockSupport.park是對Unsafe的park的包裝。
    LockSupport.park(this);
    return Thread.interrupted();
}
// 掛起線程的方法
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    // Unsafe,把當前線程掛起,進入阻塞狀態
    U.park(false, 0L);
    setBlocker(t, null);
}

釋放相關方法

// countDown方法,把計數減1
public void countDown() {
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
	// tryReleaseShared執行state減1操作
    if (tryReleaseShared(arg)) {
    	// tryReleaseShared返回true就會執行這個方法
    	// 這個方法就會把等待隊列中的線程全部喚醒
        doReleaseShared();
        return true;
    }
    return false;
}

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    // for循環+CAS自旋,把state減1
    for (;;) {
        int c = getState();
        if (c == 0)
        	// 如果發現state已經是0了,說明被其它線程操作過了,沒必要在減了
            return false;
        int nextc = c - 1;
        if (compareAndSetState(c, nextc))
        	// 如果已經減爲0了,說明倒數結束,返回true,需要去喚醒等待線程起來執行了
            return nextc == 0;
    }
}
// 喚醒所有等待的線程的方法
private void doReleaseShared() {
  /*
    * Ensure that a release propagates, even if there are other
    * in-progress acquires/releases.  This proceeds in the usual
    * way of trying to unparkSuccessor of head if it needs
    * signal. But if it does not, status is set to PROPAGATE to
    * ensure that upon release, propagation continues.
    * Additionally, we must loop in case a new node is added
    * while we are doing this. Also, unlike other uses of
    * unparkSuccessor, we need to know if CAS to reset status
    * fails, if so rechecking.
    */
   // for循環遍歷
   for (;;) {
       Node h = head;
       if (h != null && h != tail) {
           int ws = h.waitStatus;
           if (ws == Node.SIGNAL) {
               if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                   continue;            // loop to recheck cases
               // 在這個方法中調用LockSupport.unpark來喚醒線程。
               unparkSuccessor(h);
           }
           else if (ws == 0 &&
                    !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
               continue;                // loop on failed CAS
       }
       if (h == head)                   // loop if head changed
           break;
   }
}

調用CountDownLatch的await方法時,會嘗試獲取"共享鎖",不過一開始(還沒倒數結束)是獲取不到鎖的,於是線程被阻塞。而"共享鎖"的獲取條件是"鎖計數器"的值爲0,state爲0。"鎖計數器"的初始值爲count,初始化CountDownLatch時設置的值。每當一個線程調用該CountDownLatch對象的countDown()方法時,就降"鎖計數器"減1。count個線程調用countDown()方法之後,"鎖計數器"才爲0,等待獲取共享鎖的線程才能被全部喚醒繼續運行。

2、看看Semaphore是怎麼使用AQS的

state代表信號量還剩餘的數量。

acquire獲取信號量方法

public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}
// 熟悉的味道,跟CountDownLatch一樣,只不過對state的操作不一樣
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 在Semaphore中tryAcquireShared根據公平與非公平有2種實現
    if (tryAcquireShared(arg) < 0)
    	// tryAcquireShared返回值小於0,就讓當前線程去排隊等待
    	// tryAcquireShared返回值大於0,就代表本次獲取成功
        doAcquireSharedInterruptibly(arg);
}

非公平的實現
protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
	// 不關心隊列中是否有線程在等待,直接嘗試獲取信號量
	// for循環+CAS自旋
    for (;;) {
    	// 當前剩餘的信號量
        int available = getState();
        // 判斷是否當前剩餘信號量數量是否大於等於要申請的信號量數量,本次獲取夠不夠
        // acquires可以是大於1的整數,使用的時候可以自定義這個入參
        int remaining = available - acquires;
        // 如果remaining 小於0了直接返回。代表獲取失敗,信號量不夠了,就會掛起當前線程
        if (remaining < 0 ||
        	// 設置state爲當前剩餘的值
            compareAndSetState(available, remaining))
            return remaining;
    }
}
公平的實現
protected int tryAcquireShared(int acquires) {
    for (;;) {
    	// 會先判斷隊列中是否有線程在等待
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

release釋放信號量方法

public void release() {
    sync.releaseShared(1);
}
 public final boolean releaseShared(int arg) {
 	// 釋放信號量
    if (tryReleaseShared(arg)) {
    	// 跟上面CountDownLatch的doReleaseShared方法作用一樣
    	// 喚醒等待隊列的線程
        doReleaseShared();
        return true;
    }
    return false;
}

protected final boolean tryReleaseShared(int releases) {
  // for循環+CAS設置state的值
  for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}
3、看看ReentrantLock是怎麼使用AQS的

state代表重入的次數。
lock方法

public void lock() {
    sync.acquire(1);
}
 public final void acquire(int arg) {
 	// tryAcquire有公平與非公平的實現
    if (!tryAcquire(arg) &&// 如果tryAcquire返回false,說明獲取鎖沒有成功
    	// addWaiter把當前線程包裝成Node,添加到隊列中
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
非公平的實現
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    	// c==0,還沒有線程持有這把鎖
    	// CAS去嘗試set state爲1,嘗試獲取這把鎖
        if (compareAndSetState(0, acquires)) {
        	// CAS成功的話設置這把鎖的持有線程爲當前線程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
    	// 如果當前線程就是持有這把鎖的線程,說明是重入了,把state加1
        int nextc = c + acquires;
        // 如果nextc < 0代表發生了溢出
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
公平的實現
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    	// 只比非公平的實現多了一個判斷hasQueuedPredecessors
    	// 判斷隊列中是否有線程在等待,如果有別的線程在等待,則返回true
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

unlock方法

public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
	// 返回true代表鎖被釋放了
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
        	// 喚醒線程,裏面也是調用LockSupport.unpark喚醒線程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
	// 因爲是可重入鎖,state可能大於1,代表重入的次數,需要計算一下
	// releases默認是1
    int c = getState() - releases;
    // 判斷當前線程是不是持有鎖的線程
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果是重入的情況,c還不爲0,就跳過這個判斷
    if (c == 0) {
     	// 需要釋放鎖
        free = true;
        // 設置當前持有這把鎖的線程爲null
        // 代表目前沒有任何線程持有這把鎖
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

每次釋放鎖,先判斷是否是當前持有鎖的線程釋放的,如果不是,則拋異常。如果是的話,重入次數state就減1,如果減到了0,就說明完全釋放了。設置state爲0。後續就執行喚醒等待線程的方法。


  • 我的公衆號:Coding摳腚
  • 偶爾發發自己最近學到的乾貨。學習路線,經驗,技術分享。技術問題交流探討。
    Coding摳腚
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章