2012年12月11日火曜日

Semaphore

計数セマフォーです。概念的に、セマフォーはパーミットのセットを維持します。各 acquire() は許可が利用可能になるまで必要に応じてブロックし、利用可能になったらパーミットを取得します。各 release() はパーミットを追加し、場合によってはブロックしている取得側を解放します。ただし、実際のパーミットオブジェクトは使用されません。Semaphore では、利用可能な数を数えて、それに応じた処理を行うだけです。

セマフォーは、物理的または論理的な一部のリソースにアクセス可能なスレッド数を制限するためによく使用されます。たとえば、次のクラスでは、セマフォーを使用して項目のプールへのアクセスを制御します。

class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}

public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}

// Not a particularly efficient data structure; just for demo

protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE];

protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
}

protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}

}


項目を取得する前に、各スレッドはセマフォーから、項目が使用可能であることを保証するパーミットを取得する必要があります。項目の処理が完了するとスレッドはプールに戻り、パーミットがセマフォーに返され、ほかのスレッドがその項目を取得できるようになります。acquire() の呼び出し時に同期ロックは保持されません。これは同期ロックにより、項目をプールに返すことができなくなるためです。セマフォーは、プールへのアクセスを制限する必要のある同期を、プール自体の一貫性を維持するために必要な同期からは分離してカプセル化します。

値を 1 に初期化されたセマフォーは、利用できるパーミットが最大で 1 個であるセマフォーとして使用されるため、相互排他ロックとして利用できます。これは一般には「2 進型セマフォー」と呼ばれます。利用可能なパーミットが 1 個か 0 個かの 2 つの状態しかないためです。このように使用される場合、多くの Lock 実装とは異なり、2 進型セマフォーは、「ロック」を所有者以外のスレッドで解放できるという特性を持ちます (セマフォーには所有権の概念がないため)。これは、デッドロックの回復のような特殊なコンテキストで便利です。

このクラスのコンストラクタは、オプションの「公平性」パラメータを受け入れます。false に設定すると、このクラスはスレッドがパーミットを取得する順序について保証しません。特に、「割り込み」(barging) が許可されています。つまり、acquire() を呼び出すスレッドに、待機していたスレッドよりも先にパーミットを割り当てることが可能です。論理的には、新しいスレッドが待機中のスレッドのキューの先頭に配置されることになります。公平性が true に設定されると、セマフォーは、acquire メソッドを呼び出すスレッドが、呼び出しが処理された順 (先入れ先出し、FIFO) でパーミットを取得するように選択されることを保証します。FIFO 順序付けは、必然的にこれらのメソッド内の特定の内部実行ポイントに適用されます。そのため、あるスレッドが別のスレッドより先に acquire を呼び出すが、そのスレッドよりあとに順序付けポイントに達する場合があります。また、メソッドからの復帰時にも同様のことが起こる可能性があります。また、時間指定のない tryAcquire メソッドは公平性設定を尊重せず、受け入れませんが、利用可能なパーミットは取得します。

通常、リソースアクセスを制御するために使用されるセマフォーは、リソースへのアクセスができないスレッドがないよう、公平に初期化される必要があります。ほかの種類の同期制御にセマフォーを使用する場合は、公平性を考慮するよりも不公平な順序付けによるスループットの利点のほうがしばしば重要になります。

このクラスには、同時に複数のパーミットを 取得 および 解放 するための簡易メソッドもあります。公平性を true に設定せずにこれらのメソッドを使用すると、無期限に延期される危険が増すことに注意してください。

メモリー整合性効果:release() などの「解放」メソッドを呼び出す前のスレッド内のアクションは、別のスレッドで acquire() などの正常終了した「取得」メソッドに続くアクションよりも「前に発生」します。

0 件のコメント:

コメントを投稿