同時11:条件キューCondition実装分析
7584 ワード
Condition
ConditionはJ.U.Cパッケージのインタフェースであり、await、signal、signalAllの3つの主要な方法を提供しています.Objectのwait、notify、notifyAllの3つのモニタメソッドと意味が一致します.
モニタメソッドはsynchronized修飾の同期体内に配置する必要があります.Conditionインタフェースも同様です.ロックの監視範囲であるlockとunlockの間のコードブロック内で呼び出す必要があります.そうしないと、IllegalMonitorStateException異常が放出されます.
小さな栗:
public class ConditionTest {
public static class ResponseFuture {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private String response;
public boolean isDone() {//
return response != null;
}
public String getResponse() throws InterruptedException{ //
if (!isDone()) {
lock.lock();
try {
while (!isDone()) {
condition.await();//
if (isDone()) {
break;
}
}
} finally {
lock.unlock();
}
}
return response;
}
public void done(String response) {//
lock.lock();
try {
this.response = response;
condition.signal();//
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
final ResponseFuture responseFuture = new ResponseFuture();
new Thread(new Runnable() {//
public void run() {
System.out.println(" ");
try {
// ,
System.out.println(responseFuture.getResponse());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {//
public void run() {
try {
Thread.sleep(10000);//
}catch (InterruptedException e) {
e.printStackTrace();
}
//
responseFuture.done(" ");
}
}).start();
}
}
ConditionObjec
Conditionインタフェースの実装ConditionObjectは、AbstractQueuedSynchronizerの内部がAQS()にある.
public class ConditionObject implements Condition,
java.io.Serializable {
private static final int REINTERRUPT = 1;
private static final int THROW_IE = -1;
/** */
private transient Node firstWaiter;
/** */
private transient Node lastWaiter;
public ConditionObject() {}
... ...
}
げんり
ブロックスレッドのキューとスケジューリングを行うためのチェーンテーブルNodeキューを維持します.条件キューと呼ばれます.
スレッドはconditionを呼び出した.await()は、スレッドが持つロックを解放し、Nodeが条件キューに追加されてからスレッドを一時停止するように構成されます.
他のスレッドはconditionを呼び出した.Signal()の後、条件キューで最初に待機していたノードをAQS同期キューに移行し、AQS同期キューでロック取得のスケジューリングに参加し、ロックが取得されるとスレッドが起動する.
awaitプロセス
// s1
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();//
Node node = addConditionWaiter();// s2
int savedState = fullyRelease(node);// s3
int interruptMode = 0;
while (!isOnSyncQueue(node)) {//
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
s 1:スレッドは中断せずに入列を開始し、s 2に移行し、そうでなければ直接異常を投げ出す.エントリは、現在のスレッドが保持するロックをs 3に解放することに成功した.isOnSyncQueue(node)は、ノードがAQS同期キューにいるかどうかを判断し、いない場合は、そのスレッドがロックの競合に参加する資格がないことを示し、現在のスレッドを一時停止します.NodeがAQS同期キューに参加するか、nodeに対応するスレッドが中断されるまで、while{}ループを終了しません.checkInterruptWhileWaitingメソッドは、割り込みが発生したタイミングをチェックします.signalの前に発生した割り込みはTHROWを返します.IEは、InterruptedException signalを投げ出した後に発生した割り込みがREINTERUPTに戻ることを示し、記録割り込み状態がそうでなければ0に戻ることを示し、割り込みが発生してもtransferAfterCancelledWaitメソッドはnodeをAQS同期キューに移行することを示す.ACquireQued()メソッドを呼び出してAQS同期キューでロックを競合させ、ロックを取得してから割り込みの処理を開始し、reportInterruptAfterWaitメソッドではinterruptModeに基づいて例外を放出するか記録するかを選択します.
// s2
private Node addConditionWaiter() {
Node t = lastWaiter;
// CONDITION,
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
s 2:末尾ノードtが空でない場合、または状態がCONDITIONでない場合、unlinkCancelledWaiters()メソッドが呼び出されます.これは、ノードから取り外されるので状態がCONDITIONではないノードを先頭から外すことを目的としたループメソッドです.t=nullは、現在のキューか空のキューかを示し、nodeをヘッダノードfirstWaiterに割り当てます.そうしないと、nodeは末尾にチェーンされます.エンドノードlastWaiterをnodeに向け、s 1を返します.
// s3
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();//
if (release(savedState)) {//
failed = false;
return savedState;//
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)//
node.waitStatus = Node.CANCELLED;
}
}
s 3:release()メソッドで現在のロックを解放します.これは排他ロックの解放メソッドです.そのため、Conditionオブジェクトは排他ロックでの使用のみをサポートします.リリースが完了したらs 1に戻ります.
Signalプロセス
public final void signal() {
if (!isHeldExclusively())//
throw new IllegalMonitorStateException();
Node first = firstWaiter;//
if (first != null)//
doSignal(first);
}
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
s 1:first.nextWaiter=nullはキューが空であることを示し、エンドノードlastWaiterも空にします.first.nextWaiter=nullはヘッダノードをキューから取り外します.s 2に移行してノード転送を行う.転送に成功するとループが終了し、転送に成功せず、キューに待機しているノードが次のノードを転送し続けます.
// s2
final boolean transferForSignal(Node node) {
// Node.CONDITION 0,
// CONDITION
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);//
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
s 2:CASはノード状態をCONDITIONから0に設定する.Enq(node)は、AQSにおける同期キューのエンキュー方法であり、ノードを同期キューに入れる.ws>0は、ノードがキャンセルされた(中断する可能性がある)か、ノードの状態をSIGNALに設定できなかった場合、スレッドを直接起動することを示す.起動されたスレッドは、「awaitプロセスs 1」でロックの競合に参加したり、処理が中断したりします.
SignalAllはsignalロジックと同様に,ループでtransferForSignal(first)を実行するだけで,条件キュー内のノードを順次すべて同期キューに移行する.
まとめ:
1:ConditionはReentrantLock、ReentrantReadWriteLockなどの排他ロックでのみ使用できる.writerLock.ReentrantReadWriteLockなどの共有ロックでは使用できません.readerLock.
2:Conditionのメソッドは、ロックの監視範囲内、すなわちlockとunlockの間のコードブロック内で呼び出す必要があります.
コードワードは容易ではありませんて、転載して原文の接続を保留してくださいhttps://www.jianshu.com/p/e72b43ebd788