Javaスレッド池の使用と原理の詳細解


スレッド池は何ですか?
私たちはjavaを利用して新しいスレッドを簡単に作成できます。同時にオペレーティングシステムがスレッドを作成するのも大きなオーバーヘッドです。したがって、スレッドの多重化に基づいて、スレッド池の概念を提案しました。スレッドを使っていくつかのスレッドを作成し、一つのタスクを実行した後、スレッドはしばらくの間存在します。(ユーザはアイドルスレッドの生存時間を設定できます。後で紹介します。)新しいタスクが来ると、このアイドルスレッドを直接多重します。スレッド損失を廃棄する。もちろんアイドルスレッドは資源の浪費(すべてがアイドルスレッドの生存時間の制限があります)でもありますが、頻繁に廃棄スレッドを作成するよりはずっといいです。
以下は私のテストコードです。

  /*
   * @TODO      
   */
  @Test
  public void threadPool(){

    /*java          ,        50000,          
     *   CountDownLatch#coutDown()  (      1)
     *               0
    */
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    Executor pool = Executors.newFixedThreadPool(10);//          10   
    for(int i=0;i<50000;i++){
      pool.execute(new Runnable() {
        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
    }

    while(count.getCount()!=0){//    5w       

    }
    long end = System.currentTimeMillis();
    System.out.println("50        ,   :"+(end-start)+"ms");
  }


  /**
   *@TODO          
   */
  @Test
  public void thread(){
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    for(int i=0;i<50000;i++){
      Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
      thread.start();
    }

    while(count.getCount()!=0){//    5w       

    }
    long end = System.currentTimeMillis();
    System.out.println("50000        ,   :"+(end-start)+"ms");


  }

スレッドを使って5 wスレッドを実行した後は約400 msで、スレッドを使わずに実行すると約4350 msで、その効率は一斑に見える(読者は自分でテストすることができますが、コンピュータの構成が違っていますので、走ってきたデータは違っていますが、スレッドを使うと絶対にスレッドを作成するより速いです)。
javaはどうやってスレッド池を使いますか?
上のテストコードには既にスレッド池が使われています。正式に紹介します。
javaのすべてのスレッド池の最上階はExectorインターフェースであり、その唯一のexecute方法であり、すべてのタスクを実行するために、javaはまたExectorServiceインターフェースを提供しています。Exectorから継承して、この抽象的なクラスは次のAbstractExectorServiceになります。私たちがよく使うjavaスレッド池はこのクラスの例です。
上記はExectorsを使ってツール類です。これはシンタックスキャンディーです。いろいろな業務のスレッド池パラメータをカプセル化して、new操作をしてくれます。

 public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                   0L, TimeUnit.MILLISECONDS,
                   new LinkedBlockingQueue<Runnable>());
  }
上はExectors.newFixedThreadPool(10)のソースコードです。
次のポイントは、ThreadPoolExector構造方法各パラメータの意味です。

 public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               ThreadFactory threadFactory,
               RejectedExecutionHandler handler)
上のこの構造方法は一番そろっています。
ソースに基づいて、いくつかのパラメータの意味を説明します。もっと説得力があります。
以下はThreadPoolExectorメソッドで、上のインターフェースで呼び出されたexecuteの実行者です。

 public void execute(Runnable command) {
    if (command == null)
      throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
        return;
      c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
      int recheck = ctl.get();
      if (! isRunning(recheck) && remove(command))
        reject(command);
      else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
    }
    else if (!addWorker(command, false))
      reject(command);
  }

ctlはAtomicIntegerの例です。原子文を提供するCAS操作のクラスです。これはスレッド池で現在実行されているスレッドの数を記録するために使用されます。-2^29を加えると、workCountOfメソッドは絶対値を取得します。corePoolSizeより小さい場合は、addWorkersメソッドを呼び出します。したがって、スレッドを作成する方法です。addWorkdはスレッドを作成する過程で、corePoolSizeまたはmaxnumPoolSizeの値と比較します。現在動作中のスレッドの数がcorePoolSize以下であるかどうかを確認します。作成にも成功します。
スレッド池Running状態のみを簡単に議論します)。
もし運行中ライン数がcorePoolSize以上の場合、2番目のifに入ると、isRunningはSHUTDOWNと比較して、前に言ったcは現在動作しているスレッド数にマイナス2^29を加えて、現在動作しているスレッドデータが2^29に達したらその値は0、isRunningはfalseに戻り、elseはadkworldを実行します。これはスレッド池が最大で2^29スレッドの同時運転が可能であることを示しています。
workQue.offerはrunnableを待ち行列に入れ、待ち行列に参加するとrunWorkerメソッドがキューからジョブを取得して実行します。もし現在の隊列が境界行列を採用しているなら、隊列が満杯になったらofferはfalseに戻ります。これはelse ifに入ります。見てください。ここにfalseが入ってきました。ここで実行するスレッド数がmaxnumPoolSizeと比較すると、ここで実行するスレッド数がmaxnumPoolSize以上であれば、このスレッドタスクはスレッド池に拒否され、reject(command)を実行します。拒否方法には我々ThreadPool Exector構造方法のRejectExectionHandlerが使用されています。
上のソースコードの紹介を通して、ThreadPool Exectorのパラメータを紹介します。
スレッドの作成と拒否ポリシー
corepoolSize、maxnumPoolSize、BlockingQueの三つは一緒に話します。
スレッドが実行されているスレッドがcorePoolSizeより小さい場合、新しいスレッドタスクが常に新しいスレッドを作成して実行されます。corePoolSizeより大きい場合は待ち行列blocking Queにタスクを入れます。もしあなたが入ってきたBlocking Queは無界行列であるならば、これはキューに「無限多」を保存できる任務です。いつも列に参加して成功します。maxnumPoolizeと関係がないです。スレッドの池の中で最大数はSize corolです。しかし、もしあなたが入ってきたのが境界行列(ArayBlockingQue、SyncronousQue)であれば、列がいっぱいになると、スレッド数がmaxmunPoolSizeより小さいということは、スレッド数がmaxnPoolizeより大きいまで新しいスレッドを作成することです。スレッド数がmaxnumPoolSizeより大きい場合、タスクに参加するとスレッド池によって拒否されます。
RejectedExecution Handlerは戦略javaを拒否します。4つのAbortPolicyを実現しました。CallerRuns Policy、DiscrdOldest Policy、Disc Policyユーザーも自分でこのインターフェースを実現して自分の拒否戦略を実現できます。一つ目は直接に異常を投げます。trycatch処理ができます。二つ目はこの新しいタスクを直接実行します。三つ目はキャンセル待ちの中で一番古いです。第四は現在のジョブをキャンセルすることです。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。