上一講了解了 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 的加鎖過程,敬請期待!