Semaphore で遊ぶ
早いもので今日は五月五日 こどもの日です。
祝日法2条によれば、「こどもの人格を重んじ、こどもの幸福をはかるとともに、母に感謝する」となっています。
5月5日は古来から端午の節句として日本では男子の健やかな成長を祈願し各種の行事を行う風習があります。
ということで、私も男子の健やかな成長を祈願するプログラムを組むことにしました。
今さらですが、J2SE5.0 で追加された java.util.concurrent.Semaphore をつかってふざけた 夢のようなプログラム組みました。
男子の健やかな成長には素敵な女性との出会いは必要不可欠です。
だから4人の素敵な女性にデートに誘われると言う内容です。
しかし、体は一つしかありませんので一度に4人では収集付かなくなります。
そこで恭平さんにヘルプをお願いしました。(^_^;)
これでも、素敵な女性4人に対して男子は二人です。
これでなんとかするために Semaphore を使って二組のカップルまでがデートに出かけられるとします。
残りの二人の女性は先に出かけた二組のカップルのどちらか片方が帰ってきたらデートに出かけるという設定です。
つまり、男子二人というリソースに同時アクセス出来るのは素敵な女性スレッド二つまでということになります。
それが Semaphore を使えば実現できてしまうのです。
Semaphore はざっくり説明するとリソースに対するアクセスが可能か不可能かのいずれかの値をとる Binary Semaphore と
アクセス可能なリソース数を任意に設定することが出来る Counting Semaphore があります。
Java で採用された Semaphore は Counting Semaphore にあたります。
有限リソースに対して並行アクセス可能なスレッド数を自由に設定できます。
今回組むプログラムにうってつけの優れものとなっています。(^_^)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
package jp.yucchi.playboy; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author Yucchi */ class Guy { private final String[] guys = {"ゆっち", "恭平"}; private final int MAX_AVAILABLE = guys.length; private final boolean[] played = new boolean[MAX_AVAILABLE]; private final Semaphore semaphore = new Semaphore(MAX_AVAILABLE, true); // 公平性あり // private final Semaphore semaphore = new Semaphore(MAX_AVAILABLE); // 公平性なし String getGuyName() { System.out.println("待っているのは、" + Thread.currentThread().getName()); try { semaphore.acquire(); System.out.println("今からデートに行くのは、" + Thread.currentThread().getName()); } catch (InterruptedException ex) { Logger.getLogger(Guy.class.getName()).log(Level.SEVERE, null, ex); } return getGuy(); } void byeGuy(String guysName) { if (isplayed(guysName)) { semaphore.release(); } } // ReentrantLock を使う場合 ReentrantLock lock = new ReentrantLock(true); // 公平性あり private String getGuy() { try { lock.lock(); for (int i = 0; i < MAX_AVAILABLE; i++) { if (!played[i]) { played[i] = true; return guys[i]; } } } finally { lock.unlock(); } return null; } private boolean isplayed(String guysName) { try { lock.lock(); for (int i = 0; i < MAX_AVAILABLE; i++) { if (guys[i].equals(guysName)) { if (played[i]) { played[i] = false; return true; } else { return false; } } } } finally { lock.unlock(); } return false; } // // synchronized を使う場合 // private synchronized String getGuy() { // for (int i = 0; i < MAX_AVAILABLE; i++) { // if (!played[i]) { // played[i] = true; // return guys[i]; // } // } // return null; // } // // private synchronized boolean isplayed(String guysName) { // for (int i = 0; i < MAX_AVAILABLE; i++) { // if (guys[i].equals(guysName)) { // if (played[i]) { // played[i] = false; // return true; // } else { // return false; // } // } // } // return false; // } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
package jp.yucchi.playboy; import java.security.SecureRandom; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author Yucchi */ class Lovers { private final String loversName; private final Guy guy; Lovers(String loversName, Guy guyName) { this.loversName = loversName; this.guy = guyName; } void play() { Thread.currentThread().setName(loversName); try { Random random = new SecureRandom(); String guysName = guy.getGuyName(); System.out.println(loversName + " は " + guysName + " とデートに行きました。"); TimeUnit.SECONDS.sleep(random.nextInt(7)); guy.byeGuy(guysName); System.out.println(loversName + " は " + guysName + " とデートから帰ってきました。"); } catch (InterruptedException ex) { Logger.getLogger(Lovers.class.getName()).log(Level.SEVERE, null, ex); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package jp.yucchi.playboy; import java.lang.management.ManagementFactory; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * * @author Yucchi */ public class PlayBoy { public static void main(String[] args) { final int procs = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(); ExecutorService executor = Executors.newFixedThreadPool(procs); Guy guy = new Guy(); Lovers maki = new Lovers("堀北真希", guy); Lovers yui = new Lovers("新垣結衣", guy); Lovers ayame = new Lovers("剛力彩芽", guy); Lovers keiko = new Lovers("北川景子", guy); CompletableFuture<Void> lovers1 = CompletableFuture.runAsync(() -> maki.play(), executor); CompletableFuture<Void> lovers2 = CompletableFuture.runAsync(() -> yui.play(), executor); CompletableFuture<Void> lovers3 = CompletableFuture.runAsync(() -> ayame.play(), executor); CompletableFuture<Void> lovers4 = CompletableFuture.runAsync(() -> keiko.play(), executor); CompletableFuture<Void> allTask = CompletableFuture.allOf(lovers1, lovers2, lovers3, lovers4); allTask.join(); if (allTask.isDone()) { executor.shutdownNow(); System.out.println("(○・ω・)ノ-------- Are you happy? --------"); } } } |
17 行目で Semaphore を生成しています。
コンストラクタの引数はパーミット数と公平性を設定します。
今回のプログラムではパーミット数はリソース数と同じ 2 となっています。
公平性は API ドキュメントを見ると今回のプログラムの場合は優しい私はパフォーマンスより平等性を重視して true と設定しました。
ちなみに API ドキュメントによると
このクラスのコンストラクタは、オプションで公平性パラメータを受け入れます。
falseに設定すると、このクラスはスレッドがパーミットを取得する順序について保証しません。
特に、バージ(barging)が許可されています。
つまり、acquire()を呼び出すスレッドに、待機していたスレッドより先にパーミットを割り当てることができます。
論理的には、新しいスレッドが、待機中のスレッドのキューの先頭に配置されます。
公平性がtrueに設定されると、セマフォは、acquireメソッドのいずれかを呼び出すスレッドが、これらのメソッドの呼出しが処理された順序(先入れ先出し、FIFO)でパーミットを取得するように選択されることを保証します。
FIFO順序付けは、必然的にこれらのメソッド内の特定の内部実行ポイントに適用されます。
そのため、あるスレッドが別のスレッドより前にacquireを呼び出しても、そのスレッドよりあとに順序付けポイントに到達する可能性があります。
また、メソッドからの復帰時も同様です。また、時間指定のないtryAcquireメソッドは公平性の設定に従いませんが、利用可能なパーミットをすべて取得することにも注意してください。
となっています。
実は今回のプログラムではこの公平性の設定の違いというのは解りにくいのですが
食事する哲学者の問題(Dining Philosophers Problem)を Semaphore を使った簡易的なプログラムを組んでみるとよく解ります。
次に Semaphore からパーミットを取得するために acquire() メソッドを使います。
このプログラムでは 23 行目で使っています。
public void acquire() throws InterruptedException は API ドキュメントによると次のようになっています。
このセマフォからパーミットを取得します。パーミットが利用可能になるか、またはスレッドが割り込みされるまでブロックします。
パーミットが利用可能な場合はパーミットを取得してすぐに復帰するため、利用可能なパーミットの数は1つずつ減ります。
パーミットが利用可能でない場合、現在のスレッドはスレッドのスケジューリングに関して無効になり、次の2つのいずれかが起きるまで待機します。
•ほかのスレッドがこのセマフォに対してrelease()メソッドを呼び出し、現在のスレッドが次にパーミットを割り当てられるスレッドになる。
•ほかのスレッドが現在のスレッドに割り込みを行う。
現在のスレッドで、
•このメソッドへのエントリ上で設定された割込みステータスが保持されるか、
•パーミットの待機中に割り込みが発生した場合、
InterruptedExceptionがスローされ、現在のスレッドの割込みステータスがクリアされます。例外:InterruptedException – 現在のスレッドで割込みが発生した場合
これとは別に引数で取得するパーミット数を設定できるメソッドもあります。
次は取得したパーミットを解放する release() メソッドを調べてみます。
33 行目で使っています。
デートが終了したらパーミットを保持する必要はないので解放します。
public void release() は API ドキュメントによると次のように書かれています。
パーミットを解放し、セマフォに戻します。
パーミットを解放すると、利用可能なパーミットの数が1つずつ増えます。
いくつかのスレッドがパーミットを取得しようと試みている場合は、その中の1つのスレッドが選択され、解放されたばかりのパーミットが与えられます。
そのスレッドは、スレッドのスケジューリングに関して(ふたたび)有効になります。
パーミットを解放するスレッドは、acquire()の呼出しでそのパーミットを取得している必要はありません。
セマフォの適切な使用法は、アプリケーションでのプログラミング規約で確立されます。
たったこれだけでリソースにアクセスできる並行スレッド数を制限できてしまうんですねぇ・・・
このプログラムの実行結果は次のようになります。
待っているのは、堀北真希
待っているのは、北川景子
待っているのは、新垣結衣
待っているのは、剛力彩芽
今からデートに行くのは、北川景子
今からデートに行くのは、堀北真希
北川景子 は ゆっち とデートに行きました。
堀北真希 は 恭平 とデートに行きました。
北川景子 は ゆっち とデートから帰ってきました。
今からデートに行くのは、新垣結衣
新垣結衣 は ゆっち とデートに行きました。
新垣結衣 は ゆっち とデートから帰ってきました。
今からデートに行くのは、剛力彩芽
剛力彩芽 は ゆっち とデートに行きました。
剛力彩芽 は ゆっち とデートから帰ってきました。
堀北真希 は 恭平 とデートから帰ってきました。
(○・ω・)ノ——– Are you happy? ——–
さて、疑い深い私は NetBeans のプロファイラでスレッドの状態を確認してみました。( 注意:先ほどの実行結果とはことなります。)
期待通りの動きをしてくれてます。(^_^) NetBeans のプロファイア手軽に使えて便利だね。
Semaphore 使える良い娘じゃないか!
もし、あなたが 恭平さん に素敵な女性とデートさせずに全ての女性を独り占めしたいとしたらこのプログラムをどのように変更したらいいでしょうか?
答えは簡単ですね!
私はヘルプを頼んだ 恭平さん に悪いのでプログラムの変更はいたしません。
どうしてもというかたはご自由に。(^_^;)
ちなみに今回のプログラムでは
CompletableFuture<Void> lovers1 = CompletableFuture.runAsync(() -> maki.play(), executor);
として ExecutorService を使いましたが
CompletableFuture<Void> lovers1 = CompletableFuture.runAsync(() -> maki.play());
として Fork/Join Framework を使うのもありです。
こどもの日の休日スペシャルはこれでお終いです。
食事する哲学者の問題(Dining Philosophers Problem)に続く かも?
Comment
ゆっちのBlog » 食事する哲学者の問題(Dining Philosophers Problem) Semaphore で遊ぶ その2
Trackback
2015年5月5日2:50 PM(編集)
[...] Semaphore で遊ぶ [...]
Trackback URL