【Java】ThreadPoolExecutor.getActiveCount()で取得できる値はあくまで「およその数」である

【Java】ThreadPoolExecutor.getActiveCount()で取得できる値はあくまで「およその数」である

みなさん、こんにちは!

こうへい(@kohei72901660)です。

この記事では、スレッドセーフな並列処理を実現できるThreadPoolExecutorで実際にハマったことについて書きます。

ThreadPoolExecutorについて

ThreadPoolExecutorの詳しい記事については時間があるときに書きます(^^)

ThreadPoolExecutor.getActiveCount()とは

oracleのホームページには以下のように書かれてます。

public int getActiveCount()
アクティブにタスクを実行しているスレッドのおよその数を返します

出典元: docs.oracle.com – ThreadPoolExecutor

CastamThreadPoolExecutor.class

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CastamThreadPoolExecutor extends ThreadPoolExecutor{

public int activeCount;

public CastamThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
public java.util.concurrent.Future submit(java.lang.Runnable task) { ・・・①
activeCount++; ・・・②
return (Future)super.submit(task);
}
@Override
protected void afterExecute(Runnable r, Throwable t) { ・・・③
super.afterExecute(r, t);
activeCount–; ・・・④
}
}

実際にプール内にあるスレッドで実行されているタスク数を取得できます。

そもそもJavaではマルチスレッドで処理を分散させることができる!ことは知っているけど実際にどうやって実現するかわからなかった私にとってはとても便利!だと思いました(^^)

ただ、「およその数」ってなんだ。。

このことを知らなかったのが原因で実際の業務でハマりました。

実際にハマった内容

メインスレッドのみでは時間がかなりかかるであろう膨大なデータを扱う必要になったので、データを分割し、マルチスレッドで処理を並列処理させることになりました。

各スレッド内で実行するタスクでは、さらに外部プログラムを動かします。

処理を均等に振り分けるために、今回ハマったThreadPoolExecutor.getActiveCount()を使って各スレッド内で動くタスクを取得することに。

各スレッドで実行する上限数になるまでタスクを実行するようにしました。

ここで問題が発生しました。

一番初めにタスクを均等に振り分けるまでは良かったのですが、、

あるスレッドで1つのタスクが終了したことをgetActiveCountで取得判定し、上限数になるまでタスクを実行したときにgetActiveCountで実行タスク数を取得した際のタスク数が増えてない。。

上限数になっていないと判断したプログラムはgetActiveCountで取得した値が上限数になるまで1つのスレッドにタスクを割り当て続けました。

30タスク、スレッド数5、スレッドタスク上限数5

だとしたら結果的に割り当てられたタスクは

スレッド1→20タスク

スレッド2→5タスク

スレッド3→5タスク

となりました。

うん、均等に振り分けられてないですね笑

結論から話してるのでわかるかと思いますが、公式の仕様を把握していなかった私は検討違いな場所ばかり探して原因を掴むまでに時間かけすぎてしまいました。。

勉強になった!笑

解決策

以下、ソースコードになります。

ThreadPoolExecutorを親クラスにしたCastamThreadPoolExecutorを作成します。

このクラスはメインスレッドなので、実行タスク数をこのクラスで管理します。

①Runnableタスクをスレッドに送信するクラスです。タスクを送信した後はメインスレッドから離れるので今回テーマのgetActiveCountで取得する値はおよその数です。

②メインスレッドから離れる前に実行タスク数を保持する変数activeCountにインクリメントしてます。

③スレッドで実行されたRunnableタスクが実行完了時に呼ばれるメソッドです。

④実行タスク数をデクリメントしてます。

以上のCastamThreadPoolExecutorを作成することでメインスレッドから実行タスク数(離れたタスク数)を管理することができました!

意外と今回のように「およそ」みたいに曖昧に定義されているものもあることを知れて勉強になりました。

参考になれば幸いです(^^)


人気記事①:【決定版】Java初心者におすすめの入門書5選【結論:関連技術も学ぶべき】

人気記事②:現役エンジニアがおすすめするプログラミングスクール5社:無料あり