深入浅出Java Concurrency(12):ロックメカニズムpart 7信号量(Semaphore)

5951 ワード

深入浅出Java Concurrency(12):ロックメカニズムpart 7信号量(Semaphore)
Semaphoreはカウント信号量です.概念的には、信号量はライセンスセットを維持する.必要に応じて、ライセンスが利用可能になる前に、各acquire()がブロックされ、その後、ライセンスが取得される.release()ごとに1つのライセンスが追加され、ブロックされている取得者が解放される可能性がある.ただし、実際のライセンスオブジェクトは使用せず、Semaphoreは利用可能なライセンス番号のみをカウントし、対応する行動をとる.
つまり、Semaphoreはカウンタであり、カウンタが0でない場合にスレッドを解放し、0に達すると、許可されたスレッドにリクエストを追加するなど、すべてのリクエストリソースの新しいスレッドがブロックされます.つまり、Semaphoreは再入力できません.ライセンスが要求されるたびにカウンタが1減少し、ライセンスが解放されるたびにカウンタが1増加し、0に達すると新しいライセンス要求スレッドが停止します.
キャッシュプールは、リンクプール、オブジェクトプールなど、このアイデアを使用して実現されます.
インベントリ1オブジェクトプール
package xylz.study.concurrency.lock;
import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class ObjectCache {
    public interface ObjectFactory {
        T makeObject();     }
    class Node {
        T obj;
        Node next;     }
    final int capacity;
    final ObjectFactory factory;
    final Lock lock = new ReentrantLock();
    final Semaphore semaphore;
    private Node head;
    private Node tail;
    public ObjectCache(int capacity, ObjectFactory factory) {         this.capacity = capacity;         this.factory = factory;         this.semaphore = new Semaphore(this.capacity);         this.head = null;         this.tail = null;     }
    public T getObject() throws InterruptedException {         semaphore.acquire();         return getNextObject();     }
    private T getNextObject() {         lock.lock();         try {             if (head == null) {                 return factory.makeObject();             } else {                 Node ret = head;                 head = head.next;                 if (head == null) tail = null;                 ret.next = null;//help GC                 return ret.obj;             }         } finally {             lock.unlock();         }     }
    private void returnObjectToPool(T t) {         lock.lock();         try {             Node node = new Node();             node.obj = t;             if (tail == null) {                 head = tail = node;             } else {                 tail.next = node;                 tail = node;             }
        } finally {             lock.unlock();         }     }
    public void returnObject(T t) {         returnObjectToPool(t);         semaphore.release();     }
}
インベントリ1は、信号量Semaphoreに基づくオブジェクトプール実装について説明する.このオブジェクトプールは、コンストラクション関数に伝達されるcapacity個のオブジェクトを最大サポートする.オブジェクトプールには、FIFOに基づくキューがあり、オブジェクトプールのヘッダノードからオブジェクトを取り出すたびに、ヘッダノードが空であれば直接新しいオブジェクトを構築して戻る.そうでなければ、ヘッダノードオブジェクトを取り出し、ヘッダノードを後ろに移動する動きます.特に、オブジェクトの個数が切れた場合、オブジェクトが戻ってくるまで新しいスレッドがブロックされます.オブジェクトを返すときに、オブジェクトをFIFOの末尾ノードに追加し、空き信号量を解放すると、オブジェクトプールに使用可能なオブジェクトが追加されます.
実際にはオブジェクトプール、スレッドプールの原理は大体このようなものであるが、本当のオブジェクトプール、スレッドプールは比較的複雑な論理を処理しなければならないので、実現するにはタイムアウトメカニズム、自動回収メカニズム、オブジェクトの有効期間などの多くの仕事が必要である.
ここで特に、信号量は、信号が足りないときにスレッドを掛けるだけであるが、信号量が十分なときにオブジェクトを取得し、オブジェクトを返却することがスレッドが安全であることは保証されないため、リスト1では、同時の正確性を保証するためにロックする必要がある.
信号量を1に初期化することで、使用可能なライセンスが最大1つしかないため、互いに反発するロックとして使用できます.これは通常、使用可能なライセンスが1つしかないか、使用可能なライセンスがゼロの2つの状態しかないため、バイナリ信号量とも呼ばれます.このように使用すると、バイナリ信号量には何らかの属性があります(多くのLockとは異なります).つまり、所有者(信号量に所有権がないという概念のため)ではなく、スレッドによってロックを解除できます.デッドロックリカバリなどの特定のコンテキストでは便利です.
上記の意味は、あるスレッドAが信号量1の信号量を持っている場合、他のスレッドはこのスレッドがリソースを解放するのを待つしかない.この場合、信号量を持っているスレッドAは「ロック」を持っていることに相当し、他のスレッドの継続にはこのロックが必要であり、スレッドAの解放によって他のスレッドの実行が決定され、「ロック」を演じたことに相当する.のキャラクターです.
 
また、公平ロック非公平ロックと同様に、信号量にも公平性がある.1つの信号量が公平であれば、スレッドが信号量を取得する際にFIFOの順で許可される、すなわち要求の順で解放されることを示す.ここで特に、要求の順とは、要求信号量でFIFOキューに入る順であり、あるスレッドが先に信号を要求する可能性がある次にリクエストキューに入ると、セカンダリスレッドが信号量を取得する順序が、その後のリクエストよりも遅くなりますが、リクエストキューに先に入るスレッドです.これは、フェアロックと非フェアロックで多く話されています.
 
Semaphoreにはacquireに加えて、JDK APIを参照して、割り込みやタイムアウト、非同期などの特性をよりよく処理できるいくつかの類似のacquire方法があります.
同様の学習原則に従って、以下に主な実現を分析する.Semaphoreのacquireメソッドが実際にアクセスしたのはAQSのacquireSharedInterruptibly(arg)メソッドである.これはCountDownLatchの一節またはAQSの一節を参照することができる.
従って、Semaphoreのawait実装も比較的簡単である.CountDownLatchとは異なり、Semaphoreは公平信号と非公平信号を区別する.
インベントリ2公平信号取得方法
protected int tryAcquireShared(int acquires) {     Thread current = Thread.currentThread();     for (;;) {         Thread first = getFirstQueuedThread();         if (first != null && first != current)             return -1;         int available = getState();         int remaining = available - acquires;         if (remaining < 0 ||             compareAndSetState(available, remaining))             return remaining;     } }
インベントリ3非公平信号取得方法
protected int tryAcquireShared(int acquires) {     return nonfairTryAcquireShared(acquires); }
final int nonfairTryAcquireShared(int acquires) {     for (;;) {         int available = getState();         int remaining = available - acquires;         if (remaining < 0 ||             compareAndSetState(available, remaining))             return remaining;     } }
インベントリ2とインベントリ3とを比較すると、フェア信号と非フェア信号は、最初の試みが信号を取得できるか否かにおいて、フェア信号量は常に現在のスレッドをAQSのCLHキューに入れてキューする(最初の試みのときのキューのヘッダノードスレッドは、前のスレッドではない可能性が高いため、当然、同じスレッドの2回目の入力信号量を排除しない)これにより、AQSのCLHキューの順番FIFOに従って信号量が順次取得され、非公開平信号量については、初めて直ちに信号量を取得できるか否かが試みられ、信号量の残数availableが要求数(acquiresは通常1)より大きくなると、スレッドは直ちに解放され、AQSキューをキュー化する必要がなくなる.remaining<0の場合のみ(つまり信号量が足りない場合)AQSキューに入る.
したがって、非公平信号量のスループットは、常に公平信号量のスループットよりも大きいが、非公平信号量は非公平ロックと同様に「渇き死」があることを強調する必要がある.つまり、アクティブスレッドは常に信号量を取得することができ、アクティブスレッドではなく常に信号量を取得することが困難である可能性があるが、公平な信号量は常に要求されたスレッドの順序で信号量を取得するため、この問題はない.
 
 参考資料:
  •  信号量(Semaphore)の生産者および消費者モードでの使用
  • What is mutex and semaphore in Java ? What is the main difference ?
  • java.util.concurrentご存じの5つについて、第2部
  • Semahores

  • ロック機構part 6 CyclicBarrier
    目   録音する
    ロックメカニズムpart 8読み書きロック(1)
    ©2009-2014 IMXYLZ
    imxylz.com
    |賢さを求めて渇く