AQS是什麼?
AQS其實是juc包裏設計併發實現工具類的抽象類:AbstractQueuedSynchronizer
那些類繼承了AQS?
juc包裏有很多類繼承了AQS,比如我們經常使用的重入鎖:ReentrantLock.NonfairSync 和 ReentrantLock.FairSync,也就是我們經常說的非公平鎖和公平鎖;CountDownLatch.Sync ; ReentrantReadWriteLock.NonfairSync 和 ReentrantReadWriteLock.FairSync,也就是讀寫鎖
我將以 ReentrantLock.FairSync 開展源碼分析
ReentrantLock類
在看源碼的時候我們發現,真正繼承了AQS的是 ReentrantLock 的內部類 FairSync (或者說是抽象類Sync),但是真正實現了鎖的接口的是我們的ReentrantLock類,這裏使用引用關係代理了Sync,使用Sync完成了對於接口Lock的實現。
首先我們看下ReentrantLock的成員變量,其實很簡單,只有serialVersionUID 和 sync,其中這個sync就是內部繼承了AQS的抽象類,ReentrantLock 也是使用sync實現了Lock接口
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final Sync sync;
...
}
我們知道Lock接口定義了一個鎖應該提供的功能,首先我們看先Lock對於各個接口的定義(這裏我將對jdk中的註釋進行翻譯,看我英語四級的水平)
public interface Lock {
/**
* 獲得一個鎖
* 如果鎖不可用,則當前線程將出於線程調度目的而禁用,並處於休眠狀態,直到獲得鎖爲止(這裏看出,對於lock
* 接口,如果阻塞了,就應該讓出cpu資源)
*/
void lock();
/**
* 獲得一個鎖除非被中斷了
* 如果鎖不可用,則當前線程將出於線程調度目的而禁用,並處於休眠狀態,直到獲得鎖爲止,除非遇到下面的兩個情況之一
* 1. 獲得到了鎖
* 2. 被終端了
*/
void lockInterruptibly() throws InterruptedException;
/**
* 嘗試獲得鎖,如果鎖在可用狀態下將獲得鎖
* 如果獲得了鎖,返回true
* 如果沒有獲得鎖,返回fasle
* 經典的用法如下
* Lock lock = ...;
* if (lock.tryLock()) {
* try {
* // 進行串行業務執行
* } finally {
* lock.unlock();
* }
* } else {
* // 執行一些備用行爲
* }}
*/
boolean tryLock();
/**
* 在鎖可用情況下,在沒有超時和中斷的情況下獲取鎖
* 如果鎖不可用,則當前線程將出於線程調度目的而禁用,並處於休眠狀態,直到獲得鎖爲止,除非遇到下面的三個情況之一
* 1. 在沒超時和沒中斷情況下獲得了鎖
* 2. 被中斷了並響應,跑出異常
* 3. 超時了返回fasle(這裏注意,接口定義中是返回false,而不是拋出超時異常)
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* 釋放鎖
* 一個Lock實現通常會對哪個線程可以釋放鎖施加限制(通常只有鎖的持有者可以釋放鎖),如果違反了這個限制,可能會拋出一個(未選中的)異常
*/
void unlock();
/**
* 返回綁定在Lock上的Condition (所以說,我們應該維護一個實現類的Condition)
* 只有在當前線程獲得了鎖的情況下,纔可以讓condition進行waiting
* 調用了condition#wait 方法,將會在進入waiting之前釋放鎖,並且在wait返回之前重新獲取鎖
* 我理解這個東西是用來進行線程通信,
* 比如有個線程釋放了鎖,那他可以通過 condition#signal 或者 condition#signalAll 喚醒阻塞中的其他線程
*/
Condition newCondition();
}
現在我們來看ReentrantLock是怎麼實現的Lock接口的方法,我們發現所有的實現具體都是sync做的,我們不難看出,ReentrantLock 其實就是一個包裝類,通過代理的方式,藉助sync(AQS)完成了Lock的功能
public class ReentrantLock implements Lock, java.io.Serializable {
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
/**
* 這個接口調用的是Sync#nonfairTryAcquire的方法,sync如果是FairSync,爲啥也要調用
* nonfairTryAcquire?
* 首先思考下FairSync和NonFairSync的公平和不公平體現在哪裏:
* 主要體現在當有新線程試圖獲得鎖的時候如果先競爭鎖的話是非公平,如果直接放隊列,則是
* 公平鎖
* 接着我們來看Lock的經典實用場景:
* Lock lock = ...;
* if (lock.tryLock()) {
* try {
* // 進行串行業務執行
* } finally {
* lock.unlock();
* }
* } else {
* // 執行一些備用行爲
* }}
* Lock#tryLock的調用是用戶期望得到鎖,感覺就是一種特權非公平的用戶行爲(純屬個人猜測)
*/
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
}
FairSync
FairSync 和 NonfairSync 都是 ReentrantLock 的內部類,他們是 ReentrantLock 真正實現鎖功能的實現類
首先我們來看下 FairSync 的類繼承結構:
我們先看下各個抽象類大致負責的內容是什麼:
/**
* 獨佔式線程同步器,提供了創建鎖和釋放鎖的基礎操作行爲基礎,這個類是維護了一種所有權的概念
* 這個類其實很簡單,但是卻維護了一個很重要的語義,就是鎖當前被那個線程鎖獨有,AQS大量使用了這個語義
*/
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 3737899427754241961L;
/**
* 空的構造函數,說明初始化的時候的語義是改鎖沒有被任何線程鎖佔用的場景
*/
protected AbstractOwnableSynchronizer() { }
/**
* 被哪個線程佔用着
*/
private transient Thread exclusiveOwnerThread;
/**
* 設置當前被獨佔的線程,如果是null,表示沒有被任何線程佔用
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
AbstractOwnableSynchronizer 的兩個方法都是final,標識不想被任何類修改,這是個很穩定的類定義
現在我們將進入最複雜的模塊:AbstractQueuedSynchronizer
首先我們來看下這個類的成員變量
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 7373984972572414691L;
/**
* 等待隊列的頭結點,懶初始化,除了初始化,head的改變只能通過 setHead 方法,如果頭結點存在,那
* 需要保證頭結點的 waitStatus 不是 CANCELLED 狀態
*/
private transient volatile Node head;
/**
* 等待隊列的尾節點,懶初始化,只有通過 enq 方法新增新的等待節點
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* 同步器的狀態,當state <= 0的時候,說明沒有被線程持有,如果 state > 0 的時候,說明有線程持有
* 我覺得這個字段得配合 AbstractOwnableSynchronizer 的 exclusiveOwnerThread 一塊使用,如果配
* 合不好,可能會出現語義上的差異
*/
private volatile int state;
/**
* 這個值是自旋的時間闕值,用這個時間自旋比park更快(還沒太理解)。
*/
static final long spinForTimeoutThreshold = 1000L;
/**
* 特別nb的一個dll類,cas依賴這個類
*/
private static final Unsafe unsafe = Unsafe.getUnsafe();
/**
* state 的內存偏移量
*/
private static final long stateOffset;
/**
* head 的內存偏移量
*/
private static final long headOffset;
/**
* tail 的內存偏移量
*/
private static final long tailOffset;
/**
* Node 節點的 waitStatus 的內存偏移量
*/
private static final long waitStatusOffset;
/**
* Node 節點的 next 的內存偏移量
*/
private static final long nextOffset;
}
成員變量中有很多的內存偏移量: 類內存偏移量,我理解的就是一個類對象在分配了內存空間之後,各個成員的內存地址已經確定,這裏只需要設立座標系,即可得到各個成員變量穩定的座標地址,知道了一個類對象,知道偏移量,就可以快速找到成員變量對象。
梳理一遍AQS的成員變量,我們發現使用了Node類最爲等待隊列的節點,不可避免,我們需要分析下Node的數據結構
static final class Node {
/**
* 共享模式的標識
*/
static final Node SHARED = new Node();
/**
* 獨佔模式標識
*/
static final Node EXCLUSIVE = null;
/**
* 該節點的 waitState 值是 CANCELLED , 表示該線程已經被取消了
*/
static final int CANCELLED = 1;
/**
* 該節點的 waitState 值是 SIGNAL, 表示後繼節點線程需要unpark
*/
static final int SIGNAL = -1;
/**
* 該節點的 waitState 值是 CONDITION, 表示線程正在 condition 的等待中
*/
static final int CONDITION = -2;
/**
*
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* 狀態字段,只能使用一下值:
* SIGNAL:
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
volatile Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}