AQS --- 漸入佳境 一. 工作原理 二. 相關源碼: 三. 從 ReentrantLock 看 AQS:

上一講了解了 AQS 是什麼,接下來看看它到底是怎樣的結構。

一. 工作原理

AQS 使用一個 volatile 的 int 類型的成員變量來表示同步狀態,通過內置的 FIFO 隊列來完成資源獲取和排隊工作,將每條要去搶佔資源的線程封裝成一個 node 節點來實現鎖的分配,通過 CAS 來完成對 state 值的修改。

HashMap 進行 put 的時候,也不是直接存儲 key value 鍵值對,而是將 key value 鍵值對封裝成 Node 節點,然後用數組 + 鏈表 + 紅黑樹存儲 Node。AQS 也類似,將要搶佔資源的 Thread 封裝成 Node節點。

二. 相關源碼:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
   
    static final class Node {
       ……
       volatile int waitStatus;
       volatile Node prev;
       volatile Node next;
       volatile Thread thread;
       ……
    }

    private transient volatile Node head;
    private transient volatile Node tail;

    /**
     * The synchronization state.
     */
    private volatile int state;
}

看到這個是不是就清清楚楚明明白白真真切切了。首先 AQS 外層是 state + CLH 隊列,state 表示同步的狀態,默認是0,爲0時表示可以獲取鎖,不爲0時,線程就得老老實實到隊列中排隊去;CLH 隊列就是一個有頭結點和尾結點的雙端隊列,如下圖:

           +------+  prev +-----+       +-----+
      head |      | <---- |     | <---- |     |  tail
           +------+       +-----+       +-----+

AQS 的內層是一個 Node內部類,這個 Node 類主要有兩個指針 prev 和 next、一個 waitStatus 表示線程的狀、,一個 Thread 類型的變量保存等待的線程。

三. 從 ReentrantLock 看 AQS:

之前說了 AQS 是 JUC 併發包的基石,那就從我們接觸最多的 ReentrantLock 入手,揭開它的神祕面紗。

先來看看 ReentrantLock 的結構圖:

首先它實現了 Lock 接口,其內部主要是一個 Sync 內部類,這個內部類又有兩個子類,一個 FairSync 和一個 NonfairSync,分別用來實現公平鎖和非公平鎖。而這個 Sync 內部類,又是 AbstractQueuedSynchronizer 的子類。

1. 我們 new ReentrantLock 的時候做了什麼事?

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
 public ReentrantLock() {
     sync = new NonfairSync();
 }

通過這個構造方法可以知道,實際上是構建了一個非公平鎖。如果 new 的時候傳了 true,調用的構造方法就是:

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
 public ReentrantLock(boolean fair) {
     sync = fair ? new FairSync() : new NonfairSync();
 }

所以傳的是 true,構建的就是公平鎖。

2. 公平和非公平有什麼區別?

非公平鎖源碼:

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;
}

公平鎖源碼:

 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;
}

乍一看兩段代碼好像沒啥不一樣,其實不同之處在,if (c == 0)這段判斷中。公平鎖多了一個判斷條件,即!hasQueuedPredecessors(),看看這個方法的源碼:

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

這個方法也很簡單,首先是頭節點不等於尾節點,然後就是頭節點的下一個節點爲空或者頭節點的下一個節點保存的 Thread 不等於當前的 Thread。簡單地說就是看隊列中有沒有除了當前 Thread 以爲的 Thread 在等待獲取鎖,有就返回 true,否則返回 false。所以公平鎖就是多了這個判斷,其他都一樣。

下一篇文章將會從源碼層面分析 ReentrantLock 的加鎖過程,敬請期待!

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