Javaスレッドプール2のExecutorsが作成した5つのスレッドプールと使用上の注意

7874 ワード

0 x 01 ThreadPoolExecutorについて
前のブログで述べたように、Executorsはツールクラスです.スレッドプールを作成するときは、実際には次のようにします.
new  ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
  • corePoolSize:スレッドプールで永続的に保持されるコアスレッドの数は、setCorePoolSize関数で動的に変更できます.
  • runnableTaskQueue:実行待ちのタスクを保存するためのブロックキュー.ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue
  • のブロックキューを選択できます.
  • maximumPoolSize:スレッドプールで作成できる最大スレッド数.setMaximumPoolSize関数で
  • を動的に変更できます.
  • ThreadFactory:スレッドを作成するファクトリを設定します.
  • RejectedExecutionHandler:キューとスレッドプールがいっぱいになると、新しいタスクに参加できなくなり、スレッドプールが飽和します.このパラメータは飽和ポリシーを表し、デフォルトではAbortPolicyであり、新しいタスクを処理できない場合に例外を放出することを示します.JDK 1.5が提供する4つのポリシー:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy
  • keepAliveTime:デフォルトでは、非コアスレッドがアイドル状態になった後の生存時間を指し、コアスレッドに影響を及ぼさず、setKeepAliveTime関数で動的に変更できます.++ただし、allowCoreThreadTimeOut関数を呼び出すことで、この変数がコアスレッドに作用するように設定できます.++
  • TimeUnit:keepAliveTimeの時間単位.

  • 参考:方飛
    0 x 02 ThreadPoolExecutorのワークフロー
  • スレッドプール内のスレッド数がcorePoolSizeより小さく、空きスレッドがない場合、新しいスレッド実行タスクが作成され、空きスレッドが存在し、aliveである場合、スレッドが多重化されます.
  • スレッドプール内のスレッド数がcorePoolSizeに等しく、空きスレッドがなく、キューが満たされていない場合、タスクはブロックキューに追加され、
  • の実行を待つ.
  • スレッドプール内のスレッド数がcorePoolSizeに等しく、空きスレッドがなく、キューがいっぱいである場合、スレッド数がmaximumPoolSizeより小さい場合、新しいスレッド実行タスクが作成されます.
  • スレッドプール内のスレッド数がcorePoolSizeに等しく、空きスレッドがなく、キューがいっぱいである場合、スレッド数がmaximumPoolSizeより大きい場合、対応する拒否ポリシーに従ってタスクを処理する
  • keepAliveTimeが0より大きい場合、デフォルトではコアスレッド以外で現在のタスクを実行すると、すぐに回収されるのではなく、指定された時間を待つことになります.この時間内にタスクが必要でない場合は回収されます.そうでない場合は、タスクを実行します.

  • まとめて、もっと簡単に率直に言います.
  • corePoolSize範囲内のスレッド
  • を優先的に使用
  • は、次にブロックキューである.(実行スレッド数がcorePoolSizeより大きい場合はキューに追加)
  • 最後はmaximumPoolSizeの範囲内のスレッドです.(キューもいっぱいで実行スレッド数がmaximumPoolSize未満の場合、新規スレッド)
  • 0 x 03 Executorで提供されるスレッドプールの作成に関する5つの方法
    1.newFixedThreadPool
    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue());
        } 
    

    nThreads=3の場合、コアスレッド数と最大スレッド数が3であり、この方法で作成されたスレッドプールブロックキューの長さはInteger.MAX_であることを示します.VALUE.この場合、追加されたタスクがいくらあっても、スレッドプールは3つのスレッドしか提供されず、これら3つは空きの有無にかかわらずオンライン・スレッドプールで破棄されません.FixedThreadPoolスレッドプールは、タスクが密集しており、タスクの量が少ない場合に適しています.ただし、nThreadsの数が小さすぎると、ブロックされたキューがいっぱいになり、タスクがRejectされる可能性があることに注意してください.
    2.newSingleThreadExecutor
    この方法で作成されたスレッドプールを使用して、newSingleThreadExecutor内部ではF i n a l i z a bleDelegatedExecutorServiceを使用してスレッドプールを作成しています.F i n a l i z a bleDelegatedExecutorServiceはDelegatedExecutorServiceから継承されています.このクラスはパッケージクラスで、ThreadPoolExecutorをパッケージ化した後、ExecutorServiceのインタフェースメソッドが露出します.したがって、newSingleThreadExecutorが返すExecutorServiceは、newFixedThreadPool(1)とは異なり、ExecutorServiceが再構成されないことを保証します.
    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue()));
        }
    

    3.newCachedThreadPool
    public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue());
        }
    

    この方法で作成したスレッドプールは、コアスレッド数が0、最大スレッド数がInteger.MAX_であることを示します.VALUEなので、このスレッドプールのスレッドが空き状態になると60秒しか保持されず、回収されます.SynchronousQueueは要素を格納できないブロックキューであり、各put操作はtake操作を待たなければならない.そうしないと要素の追加を続行できないため、このようなスレッドプールにタスクをコミットする際に、空きスレッドがkeepAliveにある場合は、そのスレッドを直接使用しなければならない.そうしないと、新しいスレッドが作成される.このスレッドプールは、タスクが簡単で時間がかかるシナリオに適しています.
    4.newScheduledThreadPool
    この方法は他の方法とは少し異なり,その内部ではScheduledThreadPoolExecutorの構造方法を直接呼び出し,ScheduledThreadPoolExecutorはThreadPoolExecutorのサブクラスであり,その内部ではタイミング実行の論理を実現している.newFixedThreadPoolと似ていますが、このスレッドプールではDelayedWorkQueueが使用されています.DelayedWorkQueueは優先キューで、スタック構造に基づいて、キューの先頭は常に実行時間が最も前です.
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
            return new ScheduledThreadPoolExecutor(corePoolSize);
        }
        
    //ScheduledThreadPoolExecutor ThreadPoolExecutor   
    public ScheduledThreadPoolExecutor(int corePoolSize) {
            super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
                  new DelayedWorkQueue());
        }
    

    5.newWorkStealingPool
    この方法はJDK 8に新しく追加された方法で、この方法の内部にはForkJoinPoolが作成されています(このスレッドプールはForkJoinTaskと組み合わせて使用されています).ForkJoinとは?実は分治、合併で、大きなタスクを複数の小さなサブタスクに分けて、それぞれ実行して最終的に結果をまとめて合併します.では、WorkStealingとは何ですか.これは、自分のタスクを完了したワークスレッドが、アイドルではなく他のスレッドから保留中のタスクを盗むスケジューリングポリシーです.このスレッドプールはパラレル実行であり、デフォルトのパラレル量はプロセッサコア数であり、タスクは複数のプロセッサ間で分割されます.紙幅の問題は,もう展開しないで,今度は単独でこれを書きます.
    public static ExecutorService newWorkStealingPool() {
            return new ForkJoinPool
                (Runtime.getRuntime().availableProcessors(),
                 ForkJoinPool.defaultForkJoinWorkerThreadFactory,
                 null, true);
        }
    

    0 x 03カスタムスレッドプール
    Part1
    アリのJava開発マニュアルではExecutorsを使用してスレッドプールを作成することをお勧めしませんが、カスタム作成をお勧めします.
    Part2
    カスタム作成には、次のような問題があります.
    //                     。
    new ThreadPoolExecutor(5,5,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(1000));
    

    このようにスレッドプールを作成するのは危険であり、キューがいっぱいになり、追加タスクが拒否される可能性があります.したがって,ブロックキューがいっぱいになった場合の処理対策を考慮して,RejectedExecutionHandlerを自分で設定して処理することができる.またcorePoolSize=maximumPoolSizeの場合、このときkeepAliveTimeパラメータを指定することは意味がありません.これらのスレッドは永続的に保持されます.上から次のように変更すると、ブロックキューがいっぱいになることを減らすことができます.
    new ThreadPoolExecutor(5,50,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(1000));
    //        RejectExceptionHandler
    new ThreadPoolExecutor(5,50,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(1000),handler);
    

    これにより、コアスレッドが動作し、ブロックキューもいっぱいになると、キュー内のタスクを実行するために新しいスレッドが作成されます.
    Part3
    以下のような構成にも問題がある.
    new ThreadPoolExecutor(5,50,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());
    

    この構造はmaximumPoolSizeをほぼ失効させ,スレッドプールスレッドの数はずっと5である.
    Part4
    以下のような構成にも問題がある.
    new ThreadPoolExecutor(0, 50, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
    

    この構成ではcorePoolSizeとmaximumPoolSizeがほぼ失効し、新しく追加されたタスクはキューがいっぱいになるまでブロックキューに直接追加され、新しいスレッドが作成されますが、キューがいっぱいになるとOutOfMemoryExceptionになっている可能性があります.つまり、新しく追加したタスクは実行されない可能性があります.
    Part5
    また、次のようなものがあります.
    new ThreadPoolExecutor(5, 100, 60, TimeUnit.SECONDS, new SynchronousQueue<>());
    

    タスクの数について実行時間が明確に認識されていない場合、スレッドプールの作成もRejectにつながる可能性があります.たとえば、次のコードを実行します.
    ExecutorService executorService =
                    new ThreadPoolExecutor(5, 100, 0, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
            for (long i = 0; i <30; i++) {
                executorService.execute(() -> {
                    try {
                        Thread.sleep(1000 * 5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                System.out.println(i);
            }
    

    各タスクの実行時間が長すぎるため、スレッドプールの数はすぐに最大になり、101番目のタスクに追加すると例外が放出されます.
    rejected from java.util.concurrent.ThreadPoolExecutor@43556938[Running, pool size = 100, active threads = 100, queued tasks = 0, completed tasks = 0]
    

    まとめ:スレッドプールをカスタマイズする場合は、ビジネスニーズに基づいてタスク数を推定し、実行速度を合理的に設定し、スレッドプールのパラメータを合理的に構成する必要があります.さらにRejectedExecutionHandlerをカスタマイズすることが望ましい
    参照先:https://www.cnblogs.com/vhua/p/5297587.html https://juejin.im/entry/5afe36a46fb9a07aa213965b