blog

AbstractQueuedSynchronizerソースコード解析

ノード・クラスの共通プロパティ 例として、Sync クラスはロックの管理に内部的に使用されるため、実際のロックの取得と解放は Sync 実装クラスによって制御されます。...

Jan 26, 2020 · 5 min. read
シェア
  • AbstractQueuedSynchronizer クラスの共通プロパティ
    // ヘッド・ノード、これは現在ロックを保持しているスレッドと解釈できる
    private transient volatile Node head;
    // テールノードをブロックし、各新しいノードが来て、また、チェーンテーブルを形成し、最後に挿入される
    private transient volatile Node tail;
    // 現在のロックの状態を表し、0は、それが占有されていないことを意味し、0より大きいスレッドが現在のロックを保持していることを意味し、ロックが再入力することができますので、この値は、1より大きくすることができ、各再入力が1追加される
    private volatile int state;
    // は現在排他ロックを保持しているスレッドを表し、ロックが再入力されたときに現在のスレッドがすでにロックを持っているかどうかを判断するために使用できる。
    private transient Thread exclusiveOwnerThread; //AbstractOwnableSynchronizer から継承した。
    
  • static final class Node {
     // ノードが現在共有モードであることを識別する
     static final Node SHARED = new Node();
     // ノードが現在排他モードであることを識別する
     static final Node EXCLUSIVE = null;
     
     //以下のint定数はwaitStatus用である。
     // このスレッドがロックの競合をキャンセルするようにコード化する
     static final int CANCELLED = 1;
     // 現在のノードの後継ノードに対応するスレッドをウェイクアップする必要があることを示す。
     static final int SIGNAL = -1;
     // スレッドは条件を待ってウェイクアップしている
     static final int CONDITION = -2;
     // スレッドの共有ロックは無条件に伝播されるべきである
     static final int PROPAGATE = -3;
     
     // 上記の値1を取る -1 -2、-3この値が0より大きい場合、スレッドは待ちをキャンセルする。
     volatile int waitStatus;
     // 先行ノードへの参照
     volatile Node prev;
     // 後継ノード参照
     volatile Node next;
     //  
     volatile Thread thread;
    
    ノード・クラスの共通プロパティ ReentrantLockを例にとると、ReentrantLockは内部でロックを管理するために内部クラスSyncを使用します。 したがって、実際のロックの取得と解放はSyncの実装クラスによって制御されます。 Syncには2つの実装があり、Non-FairSync FairSync FairSyncのセクションを参照してください。FairSync のセクションを見てください。
  • public final void acquire(int arg) {
     if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    	 selfInterrupt();
    }
    
     protected final boolean tryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     // stateゼロはロックが占有されていないことを意味する
     if (c == 0) {
    	 // 公正なロックは、最初のスレッドが待機していないキューを判断し、スレッドが待機していない、casを使用して1に状態を設定する
     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");
     // state 
     setState(nextc);
     return true;
     }
     return false;
     }
    
    ロックの取得:tryAcquireを介してロックを取得しようとすると、取得に失敗した場合、現在のスレッドがノードにラップされ、addWaiterメソッドを介してブロッキングキューキューの末尾に参加するために、保留中のスレッドと他の操作のacquireQueued実行acquireQueuedメソッドは、スレッドをハングアップするロックの掌握に失敗した場合に対処するために、ロックを取得するためにウェイクアップされ、他の操作。ここでのforループはロック奪取に成功した場合のみ戻り、スレッドがハングアップして先行ノードにウェイクアップされた場合は、そのスレッドが先頭ノードとなり、ロック奪取に成功してループに入ります。
     final boolean acquireQueued(final Node node, int arg) {
     boolean failed = true;
     try {
     boolean interrupted = false;
     for (;;) {
     final Node p = node.predecessor();
     // が先頭ノードであり、ロックが成功裏に取得された場合は
     if (p == head && tryAcquire(arg)) {
     setHead(node);
     p.next = null; // help GC
     failed = false;
     return interrupted;
     }
     // がキュー・ヘッド・ノードでない場合、またはロック・グラブに失敗した場合、shouldParkAfterFailedAcquireメソッドはtrueを返す。
     // スレッドをハングアップするためにshouldParkAfterFailedAcquireメソッドを実行し、ロックを解放した後に先行ノードによってウェイクアップされた場合は、ここからも実行される。
     if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
     interrupted = true;
     }
     } finally {
     if (failed)
     cancelAcquire(node);
     }
     }
    
    現在のスレッドがロックをつかんでいない場合、現在のスレッドをハングアップする必要がありますか?
     private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
     int ws = pred.waitStatus;
     // Node.SIGNALウェイクアップされることを示し、先行ノードの状態は正常であり、現在のスレッドがハングアップする必要がある、直接acquireQueuedメソッドに戻る
     if (ws == Node.SIGNAL) 
     return true;
     // 0より大きいということは、待ちがキャンセルされ、先行ノードが変更されることを意味する
     if (ws > 0) { 
     do {
     node.prev = pred = pred.prev;
     } while (pred.waitStatus > 0);
     pred.next = node;
     } else { 
    	 // state0に初期化し、状態を-1
     compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
     }
     return false;
    }
    
    スレッドをハング
     private final boolean parkAndCheckInterrupt() {
     LockSupport.park(this);
     return Thread.interrupted();
     }
    
  • public final boolean release(int arg) {
     if (tryRelease(arg)) {
     Node h = head;
     if (h != null && h.waitStatus != 0)
     unparkSuccessor(h);
     return true;
     }
     return false;
    }
    
    protected final boolean tryRelease(int releases) {
    	 int c = getState() - releases;
    	 if (Thread.currentThread() != getExclusiveOwnerThread())
    	 throw new IllegalMonitorStateException();
    	 boolean free = false;
    	 // cゼロは、ロックがリエントラントではなく、ロックを解放できることを意味する。
    	 if (c == 0) {
    	 free = true;
    	 setExclusiveOwnerThread(null);
    	 }
    	 setState(c);
    	 return free;
    }
    
     private void unparkSuccessor(Node node) { 
     int ws = node.waitStatus;
     //先頭ノードの現在のwaitStatusが<0, 0に変更する
     if (ws < 0)
     compareAndSetWaitStatus(node, ws, 0);
     Node s = node.next;
     if (s == null || s.waitStatus > 0) {
     s = null;
     for (Node t = tail; t != null && t != node; t = t.prev)
     if (t.waitStatus <= 0)
     s = t;
     }
     if (s != null)
     // スレッドをウェイクアップする
     LockSupport.unpark(s.thread);
    }
    
    ロックの解放 後継ノードのウェイクアップ スレッドがウェイクアップされたら、次のコードからacquireQueuedメソッドに戻り ます。
    private final boolean parkAndCheckInterrupt() {
     LockSupport.park(this); // スレッドはここでハングアップした
     return Thread.interrupted();
    }
    
Read next