3.14 AQS
AQSは、JavaでのAbstractQueuedSynchronizerの略語です。AQSは非常に有名であるため、そのフルネームは忘れられがちです。AQSとは何かを見てみましょう。
先入れ先出し(FIFO)待機キューに依存するブロッキングロックおよび関連するシンクロナイザー(セマフォ、イベントなど)を実装するためのフレームワークを提供します。このクラスは、状態を表すために単一のアトミック{@codeint}値に依存するほとんどの種類のシンクロナイザーの有用な基礎となるように設計されています。1
AQSは、先入れ先出しの待機キューを介してロックと対応する同期を実装するためのフレームワークを提供します。AQSは、アトミック値に依存して状態を表し、同期操作の基礎を構築します。
3.14.1AQSの継承関係
AbstractQueuedSynchronizer(AQS)はAbstractOwnableSynchronizer(AOS)を継承します。まず、AOSとは何かを見てみましょう。
3.14.2 AbstractOwnableSynchronizer
AOSには、現在実行中の排他スレッドを表すプライベートexclusiveOwnerThreadスレッド属性が1つだけあり、そのサブクラスは、保護されたgetメソッドとsetメソッドを介してのみこの属性にアクセスできます。
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 3737899427754241961L;
protected AbstractOwnableSynchronizer() {
}
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
3.14.3 AQSCLHキュー
CLHキューはスピンロックキューの一種ですが、スピンロックキューとはどういう意味ですか?ヘッドノードスレッドが実行されているとき、後続のノードスレッドは回転状態にあります。
CLHキューノードのソースコードは、実際には非常に単純で、二重にリンクされたリストです。各ノードには、スレッドと、スレッドのステータスを表すwaitStatusがあります。唯一の不揮発性の変更されたnextWaiter属性もあります。属性Nodeがこのタイプの属性のSHAREDノードである場合、それは共有を意味します。このSHAREDノードのスレッドは共有可能であり、排他性がないため、行う必要はありません。 volatileキーワードで変更します。
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
volatile int waitStatus;
* 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;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
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;
}
}
AQSのCLHキューメカニズムは次のとおりです。
3.14.4AQS構成
二重にリンクされたリスト。1つは頭を指し、1つは尾を指し、さらに属性を示す状態を示します。
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
3.14.5ソースコードメソッド分析
CLHキューに新しいノードを追加し(テール挿入方法)、テールに新しいノードを追加します。
private Node enq(final Node node) {
//循环直到尾节点不为null,添加到尾部
for (;;) {
Node t = tail;
if (t == null) {
// Must initialize
//当前head为null比较并更新成新Node,是Unsafe提供的方法,可以看出头Node不存线程。
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
addWaiterメソッド
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//当尾节点不为null,直接入队列
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//当尾节点为null,循环先构建head节点然后入队列
enq(node);
return node;
}
AcquisitionQueuedメソッド
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//tryAcquire(arg)抽象方法,子类具体实现
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);
}
}
3.14.6まとめ
AQSに記載されていないメソッドがまだたくさんありますが、実際には動いていません。Javaソースコードの中で最も難しい部分として、作者もこの点を書くことを非常に心配しています。何度も読んだことがありますが、詳細はたくさんあります。理解してください。ロックを作成する前にまずAQSについて説明する必要がありますが、AQSは非常に難しいので、最初にロックを知ってからAQSを見ると理解しやすくなります。
JAVAソースAQSの説明↩︎