ユーザースレッド(ULT):スレッドはカーネルサポートなしでユーザープログラムに実装され、オペレーティングシステムコアに依存しません。アプリケーションプロセスはスレッドライブラリを使用して、ユーザースレッドを制御するスレッドを作成、同期、スケジューリング、および管理するための関数を提供します。さらに、ユーザースレッドは、スレッドライブラリを使用するアプリケーションプロセスによって作成および管理され、オペレーティングシステムのコアに依存しません。ユーザーモード/コアモードの切り替えは不要で、速度は高速です。オペレーティングシステムのカーネルは、複数のスレッドが存在する必要があることを認識していません。そのため、1つのスレッドブロックによりプロセス全体がブロックされます。(オペレーティングシステムは、内部で作成された複数のスレッドを認識しません。1つのスレッドが実行されると、オペレーティングシステムは他のユーザースレッドに追加のCPUを割り当てません)
カーネルスレッド(KLT):スレッドのすべての管理操作は、オペレーティングシステムによって完了します。カーネルはスレッドの状態とコンテキスト情報を保存します。スレッドがブロックを引き起こすシステムコールを実行すると、カーネルはプロセスの他のスレッドの実行をスケジュールできます。マルチプロセッサシステムでは、カーネルは同じプロセスに属する複数のスレッドを割り当てて、複数のプロセッサで実行し、プロセス実行の同時実行性を向上させることができます。カーネルはスレッドの作成、スケジューリング、および管理を完了するため、操作はユーザースレッドよりもはるかに低速ですが、プロセスの作成および管理操作よりも高速です。
スレッドプールを使用する理由
スレッドは希少なリソースであり、その作成と破棄は比較的重くリソースを消費する操作であり、Javaスレッドはカーネルスレッドに依存しており、スレッドの作成にはオペレーティングシステムの状態の切り替えが必要です。タスク、スレッドプールはスレッドキャッシュであり、スレッドの割り当て、スケジューリング、および監視を統合します。
スレッドプール実行フロー
たとえば、現在100個のタスクがあり、スレッドプール内のコアスレッドの数は4、スレッドの最大数は6です。
- executeメソッドが実行されると、4つのタスクが直接コアスレッドプールに入り、実行されます。
- 残りの94個のタスクがタスクキューに入ります(ArrayBlockingQueueとLinkedBlockingQueueは境界キューですが、LinkedBlockingQueueで十分です。デフォルトはInteger.MAX_VALUEです)。実行を待機しています。
- キューがいっぱいになると、残りのタスクが非コアスレッドプールで実行されます。
- キューと非コアスレッドプールの両方が満たされると、拒否戦略が実装されます。
- ThreadPoolExecutor.AbortPolicy()— java.util.concurrent.RejectedExecutionExceptionをスローします
- ThreadPoolExecutor.CallerRunsPolicy()—現在のタスクの追加を再試行すると、自動的にexecute()メソッドが繰り返し呼び出されます
- ThreadPoolExecutor.DiscardOldestPolicy()—古いタスクを破棄します
- ThreadPoolExecutor.DiscardPolicy()—現在のタスクを破棄します