ホームページ  >  記事  >  Java  >  Java におけるスレッド プールの使用法と原理の詳細な説明

Java におけるスレッド プールの使用法と原理の詳細な説明

黄舟
黄舟オリジナル
2017-10-09 09:58:261479ブラウズ

この記事では主に 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");


  }

スレッドプール内の5wスレッドで実行するには約400ミリ秒かかり、スレッドプールを使用せずに実行するには約4350ミリ秒かかります。効率は明らかです(読者はそれをテストできます)それ自体は同じですが、コンピューターの構成が異なるため、出てくるデータは異なりますが、スレッドを作成するよりもスレッドプールを使用した方が確実に高速です)。

Java はスレッド プールをどのように使用しますか?

スレッド プールは上記のテスト コードで使用されており、以下で正式に導入されます。

すべての Java スレッド プールの最上位は Executor インターフェイスであり、Executor インターフェイスは 1 つだけあり、すべてのタスクを実行するために使用されます。Java は、Executor から継承してメソッドを拡張する ExecutorService インターフェイスも提供します。抽象クラス AbstractExecutorService は ExecutorService を実装し、最後に ThreadPoolExecutor は上記の抽象クラスから継承します。私たちがよく使用する Java スレッド プールは、作成されたこのクラスのインスタンスです。

上記で使用した Executor は、さまざまなビジネスのスレッド プール パラメーターをカプセル化し、新しい操作を実行する構文糖です。


 public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                   0L, TimeUnit.MILLISECONDS,
                   new LinkedBlockingQueue<Runnable>());
  }

上記はExecutors.newFixedThreadPool(10)のソースコードです。

次のポイントはここです。ThreadPoolExecutor 構築メソッドの各パラメーターの意味について説明します。


 public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               ThreadFactory threadFactory,
               RejectedExecutionHandler handler)

上記の構築方法が最も包括的です。

以下では、ソースコードに基づいて、より説得力のあるいくつかのパラメーターの意味を説明します。

以下は ThreadPoolExecutor#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 は、アトミック ステートメントの CAS 操作を提供するクラスである AtomicInteger インスタンスで、スレッド プールで現在実行されているスレッドの数に -2^29 を加えたものを記録するために使用され、workCountOf メソッドはその絶対値を取得します。 (ソース コードで実装方法を確認できます)。この値が corePoolSize より小さい場合、addWorker メソッドが呼び出されます (新しい Worker を作成するために使用されます。Worker は Thread を作成します。スレッド作成プロセス中、addWorkd は corePoolSize または maxnumPoolSize の値の比較に従います (true が渡された場合は corePoolSize に基づいて比較され、false は maxnumPoolSize に基づいて比較されます)。値がその値以上である場合、作成は失敗します)。現在実行中のスレッドの数が corePoolSize 未満の場合、スレッド プールが作成され、正常に作成されることがわかります (
Running 状態のスレッド プールについては簡単に説明するだけです)。

実行中のスレッドの数が corePoolSize 以上の場合、isRunning が SHUTDOWN と比較される (その値 = 0) という 2 番目の if を入力します。前述したように、c は現在実行中のスレッドの数に -2 を加えたものに等しいです。 ^29 の場合、現在実行中のスレッド データが 2^29 に達すると、その値 = 0 になり、isRunning が false を返し、else で addWorkd を実行すると false が返されるため (addWorkd もそれをチェックします)、これはスレッド プールが最大 2^29 のスレッドの同時実行をサポートします (これで十分です)。

workQueue.offer(command) は、実行可能ファイルを待機キューに追加するためのもので、待機キューに参加した後、runWorker メソッドはキューからタスクの実行を取得します。現在のキューが境界付きキュー (ArrayBlockingQueue) を使用している場合、キューがいっぱいの場合、オファーは false を返し、スレッドの場合は false が渡されます。ここで実行中 この数値が maxnumPoolSize 以上の場合、このスレッド タスクはスレッド プールによって拒否され、reject(command) が実行されます。拒否メソッドは、ThreadPoolExecutor 構築メソッドの RejectedExecutionHandler (拒否戦略) を使用します。後で詳しく説明します。

上記の説明をソース コードと組み合わせると、ThreadPoolExecutor のパラメーターについての次の説明が理解しやすくなります。

スレッドプールでのスレッドの作成と拒否の戦略

corePoolSize、maxnumPoolSize、およびBlockingQueueは一緒に議論する必要があります

スレッド プールで実行されているスレッドが corePoolSize より小さい場合、新しいスレッド タスクは常に実行用の新しいスレッドを作成します。corePoolSize より大きい場合、BlockingQueue を渡すと、タスクは待機キューに追加されます。 in は無制限のキュー (LinkedBlockingQueue) です。これは、「無限に多くの」タスクを格納できるキューです。これは、maxnumPoolSize とは関係がありません。スレッド プール内の値は corePoolSize ですが、制限付きキュー (ArrayBlockingQueue、SynchronousQueue) を渡す場合、キューがいっぱいでスレッド数が maxmunPoolSize 未満の場合、スレッド数が maxnumPoolSize を超えるまで新しいスレッドが作成されます。スレッドの数が maxnumPoolSize より大きい場合、タスクへの参加はスレッド プールによって拒否されます。

RejectedExecutionHandler の拒否戦略は 4 つの AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy を実装します。ユーザーはこのインターフェイスを実装して独自の拒否戦略を実装することもできます。最初の拒否戦略は例外を直接スローするもので、2 番目の拒否戦略は新しいものです。タスクは直接実行され、3 番目のタスクはキュー内の最も古いタスクをキャンセルし、4 番目のタスクは現在のタスクをキャンセルします。

以上がJava におけるスレッド プールの使用法と原理の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。