「普通のプログラマーは、遅かれ早かれ、時々実行する必要がある小さなタスクがたくさんあるという事実を理解するようになります。」
「ゲームを書いている場合、それは個々のキャラクターが実行するアクションです。」
「Web サーバーを作成している場合、ユーザーからはさまざまなコマンドが送信されます。写真をアップロードする、目的の形式にトランスコードする、目的のテンプレートを適用するなどです。」
「遅かれ早かれ、すべての大きなタスクは、一連の小さな管理可能なタスクに分割されます。」
「この文脈を考えると、微妙な疑問が生じます。それらすべてをどのように管理すればよいのでしょうか? 1 分間に数百のタスクを実行する必要がある場合はどうすればよいでしょうか? タスクごとにスレッドを作成することはあまり意味がありません。Java マシン各スレッドにかなり多くのリソースが割り当てられます。」
「言い換えれば、スレッドの作成と破棄には、タスク自体よりも多くの時間とリソースがかかる可能性があります。」
「Java の作成者は、この問題に対して洗練された解決策を考え出しました。ThreadPoolExecutor です。
" ThreadPoolExecutor は、内部に次の 2 つの要素を含むクラスです。"
A) タスクキュー。プログラム内でタスクが発生したときにタスクを追加できます。
B)スレッド プール。これらのタスクを実行するスレッドのグループです。
「さらに、スレッドはタスクが終了しても破棄されません。代わりに、新しいタスクが出現するとすぐに開始できるようにするために、スレッドはスリープ状態になります。」
ThreadPoolExecutorを作成するときに、作成するスレッドの最大数とキューに入れることができるタスクの最大数を設定できます。つまり、スレッドの数をたとえば 10 に制限したり、スレッドの数を 10 に制限したりできます。タスクを 100 個までキューに追加しました。」
「これがThreadPoolExecutorの仕組みです:」
1) 新しいタスクを追加すると、そのタスクはキューの最後に配置されます。
2) キューがいっぱいの場合、例外がスローされます。
3) タスクが完了すると、各スレッドはキューから次のタスクを取り出し、実行を開始します。
4)キューにタスクがない場合、スレッドは新しいタスクが追加されるまでスリープ状態になります。
「ワーカー スレッドの数を制限するこのアプローチには、スレッドが多ければ多いほど相互に干渉するという利点があります。5 ~ 10 個のワーカー スレッドと長いタスクのキューを持つ方が、はるかに効果的です。急増するタスクに対して 100 個のスレッドを作成すると、メモリ、プロセッサ時間、データベース アクセスなどのリソースをめぐって互いに競合します。」
「 ThreadPoolExecutorの動作例を次に示します。」
ExecutorService service = Executors.newFixedThreadPool(2);
for(int i = 0; i < 10; i++)
{
service.submit(new Runnable() {
public void run()
{
// Here we download something big from the Internet.
}
});
}
「うーん、見えない…」
「newFixedThreadPool メソッドが呼び出されるときに、ThreadPoolExecutorオブジェクトが作成されます。」
とても使いやすいです。submit メソッドを使用してタスクを追加すると、すぐに次のことが行われます。
A) スリープ状態のスレッドがある場合は、それを起動してタスクを実行します。
B) 待機中のスレッドがない場合は、タスク用に新しいスレッドが作成されます。
C) スレッドの最大数に達した場合は、タスクをキューの最後に置くだけです。
「私は意図的に、例に「ここではインターネットから大きなものをダウンロードします」を含めました。「インターネットから大きなものをダウンロードする」タスクが 100 個ある場合、それらの多くを同時に実行するのは意味がありません。インターネット接続の帯域幅制限によって制限されます。この場合、いくつかのスレッドで十分です。上記の例では、次のようになります。」
ExecutorService service = Executors.newFixedThreadPool(2);
「大量のタスクを処理するのはそれほど難しくないことがわかりました。」
「はい。想像よりも簡単です。しかし、それについてはキムが教えてくれます。」
GO TO FULL VERSION