java同時プログラミングシリーズの原理編--JDKにおける通信ツール類Semaphore

3943 ワード

前言
javaマルチスレッド間で通信する場合、JDKは主に以下のような通信ツール類を提供しています。主にSemaphore、CountDownLatch、CyclicBarrier、exchange、Phiterといった通信類があります。各工具類の役割、原理、用法を詳しく紹介します。
Semaphoreの紹介
Semaphoreが翻訳してくるのは信号の意味です。名前の通り、このツール類が提供する機能は複数のスレッドが互いに「信号を送る」ことです。この「信号」は、intタイプのデータであり、スレッドがリソースにアクセスする数を限定するための「リソース」としてもよい。その構造関数は二つあります。一つのパラメータはスレッドアクセスリソースの数を指定します。二つのパラメータの一つは、スレッドアクセスリソースの数を指定し、一つは公平ロックであるかどうかを指定します。アンフェアロックの概念については、記事javaプログラミングシリーズの原理編-synchronizedとロックを参照してください。コンストラクタコードは以下の通りです。

  //           
  public Semaphore(int permits) {
        sync = new NonfairSync(permits);
  }
  public Semaphore(int permits, boolean fair) {
      sync = fair ? new FairSync(permits) : new NonfairSync(permits);
  }

その最も主要な二つの方法はacquire()とrelease()である。acquire()方法はpermitを申請しますが、release方法はpermitをリリースします。もちろん、複数のacquireを申請したり、複数のreleaseをリリースしたりすることもできます。毎回acquireで、permitsは一つ以上減少します。0に減少し、他のスレッドがacquireに来たら、他のスレッドがあるまでこのスレッドをブロックします。
Semaphoreの使用
Semaphoreは主にスレッドアクセスリソースの数を制御するためのシーンです。例を挙げると、合併の条件の下で、3つのスレッドにあるタスクを実行させたいだけです。サンプルコードを見てください。
public class SemaphoreDemo {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 10; i++) {
            new Thread(new MyThread(i,semaphore)).start();
        }
    }

     static class  MyThread implements Runnable{

        private int id;//   ID 
        private Semaphore semaphore;

        public MyThread(int id, Semaphore semaphore){
            this.id = id;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                //     permit  
                semaphore.acquire();
                //                
                System.out.println(String.format("      %d,   %d         , %d        。",
                        id,semaphore.availablePermits(),semaphore.getQueueLength()));
                Random random = new Random();
                //      ,      
                Thread.sleep(random.nextInt(1000));
                System.out.println(String.format("  %d     ",id));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //    ,    
                semaphore.release();
            }
        }
    }
}
出力結果:
現在のスレッドは1で、残りのスレッドリソースは1つしかありません。0スレッドが待機中です。現在のスレッドは0で、残りは2つのスレッドリソースが使用できます。0スレッドが待機中です。現在のスレッドは2で、残りは0スレッドのリソースがあります。0スレッドが待機中です。スレッド2は、リソースが現在リリースされているスレッドが3であり、残りは0スレッドのリソースがあり、6スレッドが待機中である。スレッド1は、リソースをリリースしています。現在のスレッドは4です。残りは0スレッドのリソースがあります。5スレッドが待機しています。スレッド0は、リソースが現在リリースされているスレッドが5であり、残りは0スレッドのリソースが使用可能であり、4スレッドが待機中である。スレッド3は、リソースが現在リリースされているスレッドが6であり、残りは0スレッドのリソースが使用可能であり、3スレッドが待機中である。スレッド4は、リソースが現在リリースされているスレッドが7であり、残りは0スレッドのリソースが使用可能であり、2スレッドが待機中である。スレッド5は、リソースが現在リリースされているスレッドは8であり、残りは0スレッドのリソースがあり、1スレッドが待機中である。スレッド8は、リソースが現在リリースされているスレッドが9であり、残りは0スレッドのリソースが使用可能であり、0スレッドが待機中である。スレッド7は、リソーススレッド6をリリースし、リソーススレッド9をリリースしました。
この結果から、最初にこの3つのリソースを奪ったスレッドは1、0、2であり、他のスレッドは待ち行列に入っていることが分かります。その後、スレッドがあるたびにリソースが解放され、待ち行列のスレッドがリソースに奪われます。Semaphoreデフォルトのacquire方法はスレッドをキューに入れ、中断異常をスローします。しかし、割り込みを無視する方法もあります。またはブロック列に入らない方法もあります。
 //     
 public void acquireUninterruptibly()
 public void acquireUninterruptibly(int permits)

 //        ,    CAS
 public boolean tryAcquire
 public boolean tryAcquire(int permits)
 public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException
 public boolean tryAcquire(long timeout, TimeUnit unit)
Semaphoreの原理
Semaphore内部にはAQSを継承した同期器Syncのメンバー変数があり、tryAcquire Shared方法を書き換えました。この方法では、資源の獲得を試みます。取得に失敗した場合(現在のリソースの数より小さいリソースが欲しい)、負の数が返されます。そして現在のスレッドはAQSの待ち行列に入ります。具体的なコードロジックはJDK 1.8の中でjava.util.co ncurrentが包むSemaphore類を確認してください。
参照リンク
ここで感謝しています。幸いにも各工場の大神さんたちからのソースプロジェクトが深入にJavaマルチスレッドから出てきて、マルチスレッドの知識に対してもっと深い理解ができます。文章の中で、どこか適当でないところや疑問があったら、メッセージを残して、お互いに勉強しましょう。ありがとうございます