Androidアプリ開発を想定した上で一般に使えるJavaのマルチスレッディングについてのメモ書きです。
全部を解説するにはページが足りないので、タスクキューの実装を例に触れていきます。
(Java SE 6以上)
Executor
まずはExecutorについての説明を。Java SE 6でスレッドを生成する方法は
・new Thread … Threadインスタンスの直接的な生成
・Executorインタフェースを実装したクラスのインスタンスを生成し、別スレッドでの処理を依頼する(executeメソッド等)
・SwingWorkerを用いる
Swingに関しては今回はAndroid環境を想定しているので除外します。
Threadインスタンスを直接生成するのは古くから用いられてきた方法ですが、単純故に自由度が大きい反面周期実行や遅延実行、スレッドプールなどの実装を全て自分で行わないといけません。よほどこだわりのマルチスレッディング実装があるので無ければ現在こちらを使う利点はないです。なのでExecutorをということになるのですが、Executorインタフェースは内部でスレッドを保持し続けているため、shutdownできません。ExecutorServiceでは、shutdownメソッドを呼ぶことで終了することが可能となります。なので普通はこちらを使います。これは特に意識しなくていいです。
タスク
Javaでは組み込みのタスクインタフェースとしてRunnableとCallableがあります。サブスレッドで処理をした後戻り値をメインスレッドに返さなくてよいならRunnbale、返さないといけないならCallableです。Callableタスクは戻り値をFutureインスタンスに格納して返します。これについては後述。
スレッドプール
今回はとにかく楽にスレッドセーフなタスクキューを実装することを目標とします。また今回のタスクキューは単一スレッドではなくスレッドプールに投げ込む方式でいきます。
それではいきなりですがExecutorServiseインスタンスを管理するクラスです。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; public class MyTaskQueue { private static int count = 1; private static ExecutorService executor; static { executor = Executors.newFixedThreadPool(4, new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "MyThread " + count++); } }); } }
Executorsクラスは静的メソッドで新しいExecutorServiseインスタンスを返します。
newFixedThreadPool(int, ThreadFactory)は固定数の再利用可能なスレッドプールを持つExecutorServiseです。ThreadFactoryはオーバーロードで省略できますが今回はスレッドに名前をつけるために渡しています。また今回は4スレッドのスレッドプールとしています。あまり増やしても結局これはCPUやらマシンスペックやらに寄ってくるので2~8程度が妥当なんではないでしょうか。
それでは次はタスクを追加できるようにします。
タスクキューイング
説明下手なのでとりあえず実装したものをどうぞ。とにかくRunnableを放り込んで、裏で処理するだけです。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; public class MyTaskQueue { private static int count = 1; private static ExecutorService executor; static { executor = Executors.newFixedThreadPool(4, new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "MyThread " + count++); } }); } public static void addTask(Runnable task) { executor.execute(task); } public static void main(String args[]) { for(int i = 0; i < 10; i++) { final int i1 = i; addTask(new Runnable() { @Override public void run() { System.out.println(i1); } }); } } }
これを実行した結果がこちら
0 4 5 6 7 8 9 2 1 3
全然順序違いますね。これはなぜかというとnewFixedThreadPoolで生成されるExecutorServiseが内部に持つThreadPoolExecutorというクラスが持っているキューが完全なFIFOじゃないからです。詳しくはこちら=> http://kiyorin-net.blogspot.jp/2010/05/threadpoolexecutor.html を参照してください。Executor内のキューのサイズはInteger.MAX_VALUEなのでまずいっぱいになることはないと思います。また非同期処理で順序が保証される必要がある場面というのはあまりないと思うのでこれで問題ないかと。
長くなったのでなんだか微妙ですが今回はここまで。CallableとExecutorを使ったAndroidにおけるUIスレッド処理について今度書きます。