AQS

AbstractQueuedSynchronizer

基礎的同步器,主要實現了對等待隊列的管理(入隊出隊,獨佔/共享模式下對等待節點的喚醒等),而具體的獲取、釋放的判斷邏輯由子類實現

等待隊列

節點類是AbstractQueuedSynchronizer一個內部類Node

該隊列是一個雙向隊列,AQS的head變量指向頭結點(不是等待節點,head.next纔是隊列中第一個等待節點),tail變量指向尾節點(等待節點)

而入隊、出隊操作,即爲對tail和head的更新操作均爲通過Unsafe的CAS操作進行更新

隊列是延遲初始化,在加入節點的時候才進行初始化

acquire、release

AQS中提供了state屬性(int)作爲用於同步的狀態

具體判斷能否獲取、釋放的邏輯方式(基於state屬性)都是abstract(如
tryAcquire,tryAcquireShared,tryRelease,
tryReleaseShared),交由子類實現

acquire

獲取釋放分爲兩個模式:

  • 獨佔模式
  • 共享模式:在acquire獲取成功後,如果下一個等待節點是共享模式的,則會喚醒該節點(正是該特性,讓共享模式下,能同時獲取成功)

都是通過CAS的方式保證併發安全(已進入等待隊列的節點,按照先進先出的規則執行)。

獲取的流程:

  1. 調用try方法嘗試獲取,獲取成功直接返回,否則進入步驟
  2. 以指定模式(獨佔 or 共享)創建節點並添加到AQS隊列中
  3. 判斷當前節點是否爲第一個等待節點,如果不是進入步驟4,如果是則把head指向該節點(setHead方法,相當於該節點已經出隊了,共享模式此時會把後續共享模式的節點喚醒),然後返回interrupted(默認爲false,會在步驟5中被修改),進入到步驟6
  4. 調用shouldParkAfterFailedAcquire,判斷前面前面的等待節點是否有效,如果前面的節點已被取消先清理前面的節點再到步驟3,如果前面的節點狀態不是等待喚醒則更新狀態再到步驟3,如果前面的節點正常則掛起當前線程,到步驟5
  5. 如果步驟4中掛起了線程,從掛起中恢復,判斷是被中斷還是通過unpark導致的喚醒(parkAndCheckInterrupt),如果是中斷導致的把interrupted置爲true(會影響到後續是否掛起線程),進入步驟3
  6. 如果interrupted爲true(表明在等待的時候,被別的線程中斷了),掛起該線程
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

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;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

值得注意的是,步驟5中爲什麼從中斷恢復後,還要判斷被喚醒還是中斷導致的恢復?開始看的時候看得很迷茫,爲什麼要這麼判斷,後面看了LockSupport.park方法,裏面註釋提到了,對該線程的LockSupport.unpark或者Thread.interrupt方法都會導致線程中掛起中恢復

release

釋放的流程:

  1. 調用try方法嘗試釋放,釋放成功進入下一步驟,否則直接返回
  2. 把head節點狀態置爲0,從等待隊列中取出第一個未被取消的等待節點,並喚醒該節點

獨佔模式的釋放代碼:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

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;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

基於AQS的同步類

目前java中基於AQS的有以下幾個:

  • ReentrantLock
  • ReentrantReadWriteLock
  • Semaphore
  • CountDownLatch
  • ThreadPoolExecutor

Lock:ReentrantLock

ReentrantLock在構建的時候,可以以兩種模式創建,主要是在tryAcquire中是否需要判斷前面有沒有等待者:

  • 公平模式:先進先出,先等待的會先去到鎖 非公平模式:不保證先等待的會先得到鎖,鎖剛釋放並此時有獲取鎖的線程會先得到鎖,減少了線程喚醒的開銷

公平模式的tryAcquire:

  1. 判斷state是否爲0,如果是並且是第一個等待者,則獲取成
  2. 如果不是第一個等待者,再判斷該線程是否與擁有鎖的線程爲同一線程,如果是則state屬性加1(acquire),獲取成功(可重入鎖)
protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            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;
    }
}

非公平模式下的獲取:

  1. 判斷state爲0,如果是則直接獲取鎖成功
  2. 與公平模式相同
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) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

Lock:ReentrantReadWriteLock

ReentrantReadWriteLock也可以用公平、非公平兩種模式創建

同一個線程可同時擁有讀、寫鎖

總結:
獲取讀鎖時與寫鎖關係:(回顧下上面AQS的acquire中,共享模式下獲取成功,只要下一個也是共享模式的節點,會繼續喚醒該節點)

  • 當前有本線程的寫鎖,獲取成功
  • 當前有非本線程的寫鎖,獲取失敗,添加到AQS等待隊列
  • 公平模式下,當前無寫鎖,前面有等待者,進入等待隊列,沒有等待者則獲取成功
  • 非公平模式下,如果當前第一個等待者在等待寫鎖,則進入等待隊列(直到該節點前面所有的寫節點執行完成,就輪到該節點執行),否則獲取成功

獲取寫鎖時與讀鎖關係:

  • 當前有讀鎖,添加到AQS等待隊列
  • 當前有寫鎖,如果不是本線程的,添加到AQS等待隊列;如果是本線程的,獲取成功
  • 當前沒有讀鎖、寫鎖,獲取成功

ReentrantReadWriteLock包含了兩個鎖:讀鎖和寫鎖,並且是用的同一個同步器,從上面AQS可知,AQS內部只有一個state屬性來表示鎖,因此這裏把int型的state分成兩半使用,前16位用來控制寫鎖的數量,後16位作爲讀鎖的數量

/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;

public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

/** Returns the number of shared holds represented in count  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

寫鎖

寫鎖是獨佔鎖,

獲取流程:

  1. 判斷當前是否有讀寫線程,如果有進入步驟2,沒有則到步驟4
  2. 如果有讀線程或者有非自己的寫線程,返回false,否則進入步驟3
  3. 如果已經達到最後數量,拋出異常,否則更新state狀態,返回ture(獲取成功)
  4. 如果是公平模式,需要判斷前面是否還有等待寫的線程,有則返回false,沒有則更新狀態、更新當前獨佔線程,返回true
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

釋放流程比較簡單,判斷是否爲擁有者,然後更新狀態、獨佔線程即可

讀鎖

從下面流程基本可以看到,讓讀線程堵塞等待的原因有三個

  • 有非當前線程的寫線程
  • 讀線程數已滿(65535)
  • 公平模式下,前面有等待者;非公平模式下,第一個等待者爲寫線程

readHolds是ThreadLocal類型,用於記錄該線程獲取了鎖的數量

獲取流程:

  1. 判斷當前是否有非自己的寫線程,如果有則直接返回false,沒有則到步驟2
  2. 如果是公平模式,則判斷前面是否有等待者;如果是非公平模式,則判斷當前第一個等待者是否爲寫線程(如果等一個等待者是讀線程,則代表可以直接獲取讀鎖,不用等待,因爲是非公平);如果滿足條件,並且沒有超過讀的最大數量,則進入步驟3,不滿足進入步驟4
  3. 更新state的值,成功後根據情況更新firstReader 、firstReaderHoldCount、cachedHoldCounter(最後一次獲取成功讀線程信息),返回1(獲取成功)
  4. 調用fullTryAcquireShared方法重試(重新跑了一遍1-3的不步驟),結束後退出

對步驟4還是比較疑惑,他差不多就是1-3步驟的重複,根據他的解釋說也是一個重試步驟,但是爲什麼需要這個重試不大懂

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

final int fullTryAcquireShared(Thread current) {
    /*
     * This code is in part redundant with that in
     * tryAcquireShared but is simpler overall by not
     * complicating tryAcquireShared with interactions between
     * retries and lazily reading hold counts.
     */
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

釋放流程,基本就是根據情況更新firstReader、firstReaderHoldCount信息,並且根據readHolds中判斷本線程是否已經不再擁有任何讀鎖,如果沒有了,從readHolds中去掉,最後更新state信息

Semaphore

整體邏輯比上面對的可重入鎖和讀寫鎖都簡單了很多,在初始化的時候,可以指定state的值,即可以被無釋放的acquire多少次,並且信號量不是可重入的,因此也不用判斷是否該線程已獲取

與讀寫鎖中的讀鎖一樣,是以共享模式進行獲取釋放

信號量也支持指定公平\非公平模式,因此在公平模式下還需要判斷是否有前面的等待者

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            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;
    }
}

protected final boolean tryReleaseShared(int releases) {
    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;
    }
}

CountDownLatch

這個跟上面3有點不一樣,就是上面3類似於通行證,獲取了才能執行,而這邊是釋放了才能執行

創建的時候,會把state初始化一個指定值,await相當於等待state變爲0,而countDown就是給state減1

而還有個注意的是await提供了兩個版本:

  • public void await() throws InterruptedException:等待過程中被中斷了,會拋出異常
  • public boolean await(long timeout, TimeUnit unit) throws InterruptedException:在上面的基礎上,增加了可以指定等待的時長
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

public void countDown() {
    sync.releaseShared(1);
}

protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

ThreadPoolExecutor

線程池中的Worker繼承於AbstractQueuedSynchronizer,是一個不可重入鎖

而爲什麼Worker要用到AQS?
ThreadPoolExecutor在執行shutdown命令的時候,這個命令是會允許當前正在執行的任務完成,因此通過AQS來防止競爭,不可重入也是爲了防止此時tryLock成功

Worker的tryAcquyre和tryRelease方法:

protected boolean tryAcquire(int unused) {
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

protected boolean tryRelease(int unused) {
    setExclusiveOwnerThread(null);
    setState(0);
    return true;
}

shutdown:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章