スレッド多重化:スレッドプールノート


スレッドの多重化:スレッドプール


スレッドプールの概要


スレッドプールとは?


JDBCに触れたことがある人は、データベース接続プール(例えば、c 3 p 0、Druidなど)を聞いたことがあるに違いありません.実は私の理解では、両者はあまり差がありません.しかし、スレッドプールに置かれているのはスレッドだけです.スレッドは軽量レベルのツールですが、作成と閉じるには時間がかかります.また、大量のスレッドがメモリリソースをプリエンプトします.盲目的な大量の資源はシステムに大きな圧力をもたらす.スレッドプールには、アクティブなスレッドが一定数存在します.スレッドの作成は、スレッドプールから取得された空きスレッドになります.スレッドを閉じると、スレッドプールにスレッドを返すようになります.

JDKによるスレッドプールのサポート


JavaはExecutorsを通じて5種類のスレッドプールを提供し、それぞれ:
  • newCachedThreadPoolキャッシュ可能スレッドプールを作成します.スレッドプールの長さが処理の必要を超えた場合、空きスレッドを柔軟に回収できます.回収可能でない場合は、新しいスレッドを作成します.
  • newFixedThreadPoolスレッドプールを作成し、スレッドの最大同時数を制御し、超過したスレッドがキュー内で待機します.
  • newScheduledThreadPool定長スレッドプールを作成し、タイミングおよび周期的なタスク実行をサポートします.
  • newSingleThreadExecutorは、一意の作業スレッドでのみタスクを実行し、すべてのタスクが指定された順序(FIFO、LIFO、優先度)で実行されることを保証する単一スレッド化されたスレッドプールを作成します.
  • newSingleThreadScheduledExcutor単一スレッド化スレッドプールを作成し、タイミングおよび周期的なタスク実行をサポートします.

  • スレッドプールの使用


    まず簡単に使いますが、これは特別なところはありません.newFixedThreadPoolが作成したスレッドプールが一定長であることを覚えておくだけで、スレッドの最大同時数を制御でき、超えたスレッドはキューで待機します.newCachedThreadPoolで作成されたスレッドプールは無限大であり、2番目のタスクを実行するたびに最初のタスクが完了すると、新しいスレッドを作成するのではなく、最初のタスクを実行するスレッドが多重化されます.

    タイミングタスク

    newScheduledThreadPoolは、タイミングおよび周期的なタスク実行をサポートし、そのソースコードを表示します.主に以下の3つの方法があります.
  • schedule():所定の時間にタスクをスケジュールします.
  • scheduleAtFixedRate()とscheduleWithFixedDelay():タスクを周期的にスケジュールしますが、両者に違いがあります.

  • scheduleAtFixedRate()とscheduleWithFixedDelay()の違い
  • の2つのスケジュールの違い:
  • FixedRate方式:以上のタスクの実行開始時間を起点とし、その後の遅延時間後に次のタスクを呼び出す.
  • FixedDelay方式:前のタスクが終了した後、遅延時間を経てタスクスケジューリングを行います.

  • タスク実行時間がスケジューリング時間を超えると、
  • FixedRate方式:スケジューリング時間が短すぎると、タスクは前のタスクが終了するとすぐに呼び出されます(タスクスタックのフィールドは表示されません).
  • FixedDelay方式: = + に厳格に従います.


  • タスクに異常が発生した場合、後続のすべてのサブタスクはスケジュールを停止します.そのため、異常がタイムリーに処理され、周期的なタスクの安定したスケジューリングに条件を提供することを保証しなければならない.

    スレッドプールのレコードについて


    ポリシーの拒否


    スレッドプールを作成するコアクラスThreadPoolExecutorには、拒否ポリシーを指定するパラメータがあります.拒否ポリシーは、システムの過負荷運転時の救済策であり、通常は圧力が大きすぎるため、つまりスレッドプールのスレッドが切れ、待機キューがいっぱいになっていることです.JDKは4つの拒否ポリシーを提供しています.
  • AbortPolicyポリシー:異常を直接放出し、システムの正常な動作を阻止します.
  • CallerRnsPolicyポリシー:スレッドプールが閉じていない限り、破棄されたタスクは呼び出しスレッドで直接実行されます.この方法では、タスクは本当に破棄されませんが、タスクコミットスレッドのパフォーマンスは急激に低下します.
  • DiscardOldestPolicyポリシー:最も古い要求、すなわち実行されるタスク(キュー待ちキューのキューヘッダ)を破棄し、現在のタスクを再送信しようとします.
  • DiscardPolicyポリシー:処理できないタスクを直接破棄します.
  • カスタムポリシー:RejectedExecutionHandlerインタフェースを独自に拡張します.

  • スレッド拡張

    ThreadPoolExecutorは拡張可能なスレッドプールであり、beforeExecute()afterExecute()、およびterminated()がスレッドを制御することができる.
    protected void beforeExecute(Thread t, Runnable r) { }
    protected void afterExecute(Runnable r, Throwable t) { }
    protected void terminated() { }

    これは3つのprotectedの空の方法で、サブクラスを拡張することができることを明らかにしています.*タスクを実行するスレッドでは、beforeExecuteおよびafterExecuteなどのメソッドが呼び出されます.これらのメソッドでは、ログ、タイミング、監視、または統計収集の機能を追加することもできます.*正常に動作しても、例外が放出されても、afterExecuteが呼び出されます.ただし、Eorrorを投げ出すと、メソッドは呼び出されません.またはbeforeExecuteRuntimeExceptionを投げ出すと、タスクは実行されず、このメソッドも呼び出されません.*terminatedについては、オンライン・スレッド・プールがクローズを完了した場合(すべてのタスクが完了し、すべてのワーカー・スレッドがクローズされた場合)、Executorがライフサイクルで割り当てた様々なリソースを解放するほか、情報通知、ログ・レコードなどの機能も実行できます.

    補足

  • スレッドプールを使用して「食べられる」異常スタック情報スレッドプールを使用してスレッドをコミットすると、異常スタック情報が「食べられる」現象が発生する可能性があります.解決方法:
  • submit()を放棄し、execute()に変更します.
  • submit()メソッドがクラスを返すget()メソッドを取得します.
    Future future = pools.submit(new Thread());
    future.get();
  • ThreadPoolExecutorスレッドプールを拡張し、タスクをスケジュールする前に、タスクスレッドをコミットするスタックメッセージを保存させます(スレッドプールスレッドを書き換える呼び出し方法です).

  • カスタムスレッド:ThreadFactoryこのインタフェースには、主にスレッドプールによって新しいスレッドが呼び出される方法が1つしかありません.
  • 最適化スレッドプールスレッド数『Java Concurrency in Practice』では、スレッドプールサイズを推定する経験式が与えられています(Javaでは、利用可能なCPU数をnewThread(Runnable r)で取得できます).
  • Ncpu = CPU 
    Ucpu =  CPU ,0 <= Ucpu <= 1
    W/C =  
     , :
    Nthreads = Ncpu * Ucpu * ( 1 + W/C )

    参考資料

  • 《実戦Java高同時プログラム設計》(葛一鳴郭超著)