2013年1月11日金曜日

ThreadPoolExecutorってこんなやつかも

昨日、帰ったらThreadPoolExecutorの動きを確認するサンプルコードを書くつもりだったのですが、仕事の中で満足するくらい確認できてしまって、結局家ではやりませんでした。

自分の理解したThreadPoolExecutorはAndroidのものです。たぶん、一般的なJavaでも同じだと思いますが。さらに、Android2.xまでのAsyncTask内で使われているような使い方をした場合の動作です。

http://developer.android.com/reference/java/util/concurrent/ThreadPoolExecutor.html

ThreadPoolExecutorというのは、大きく2つため込むものがあるようです。
1つは名前が表す通り「スレッド」、もう1つは「実行前のタスク」です。タスクっていうのはRunnableですね。
コンストラクタで指定するいくつかの値がありますが、corePoolSize、maximumPoolSizeはスレッド数に関するものです。keepAliveTime、unitはcorePoolSizeを超えたスレッドの生存期間を表しているようです(ここはあまり調べませんでした)。workQueueは実行前のタスクがためられるキューです。キューのインスタンスを渡すので、ここの長さも使用する側で決めることができます。

ちなみにAndroid2.xまでのAsyncTaskでは以下のように設定されていました。

corePoolSize … 5
maximumPoolSize …128
keepAliveTime、unit … 1秒
BlockingQueue<runnable> workQueue … 長さが10固定のキュー

このときexecute()を行うとどのようになるか。

(1) Executorが保持するスレッドがcorePoolSize以下のとき

スレッドを作って実行します。poolSizeが+1されます。
getPoolSize()は内部のスレッド数を返します。
getActiveCount()は動作中のスレッド数を返します。
ここは問題ありませんね。

(2) Executorが保持するスレッドがcorePoolSizeを超えているとき

workQueueの状態で動作が変わります。

(2-a) キューにタスクを格納可能なとき

キューに詰めて終わりです。キューに詰められたタスクは、実行中のスレッドが空けば取り出されて処理されます。

(2-b) キューにタスクを格納できないとき(キューが一杯のとき)

キューからタスクを1つ取り出して、新しくスレッドを作って実行します。新しいタスクはキューに詰められます。
ただし、スレッド数がmaximumPoolSizeを超えるようだと、例外が発生して終わりです。
ここでスレッドが作られるため、getPoolSize()を呼ぶとcorePoolSizeを超えた数が返されます。スレッド数は最大でmaximumPoolSizeまで増えますが、keepAliveTime、unitで指定した時間を超えて何もしないスレッドは破棄されていきます。

書いてみると単純ですね…。
すっごく苦労したんですが。


今回、不具合でタスクが完了しない(無限ループに陥ってた)ものがあり、スレッドに居座り続けるという事態が発生していました。しかも、何度も呼ばれる処理だったために、徐々にスレッド数を消費していきます。
その不良スレッドがcorePoolSizeを超えて、なおかつキューに空きがある場合、新しく呼ばれた処理はキューに詰められるだけで何もしません。恐ろしいですね。そんな不具合でした。

大変でしたが、コードをたくさん読めて満足しました。

0 件のコメント:

コメントを投稿