ThreadExecutorServiceを使って、安心・安全にマルチスレッド処理を行う

  • 30
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

この記事はAndroid Advent Calendar 2014の24日目の記事です。

昨日は@futaboooでした。会社の自席から3つ進んだ所に居ます。
明日は@yuyakaidoです。会社の自席から3つ進んだ斜め先に居ます。

12月もとうとう24日ですね!
本日はみなさん共通の知り合いの誕生日前夜ですね。お祝いですね。
※共通の知人:キリスト


では、本題に入りましょう。今回はスレッド周りの話をします。

【何故、クリスマスイブにスレッドの話】

そりゃもう、マルチスレッドを巧みに扱う人はこんな大切な日でもマルチスレッド並みにN股くらい仕掛けているんだろうなぁ。という話ですよ。

さらにはスレッドセーフなので、同時刻に複数人と会っても彼氏・彼女達に気付かれることも無いのでしょうね。素晴らしい。

さて、そんなシングルスレッド。いや、ゼロスレッドの画面の前のみなさんも、マルチスレッドの扱いに長けることさえできれば来年は明るいクリスマス(イブ)が待っていますよ。(スレッドセーフでお願いします)

さてさて、話を戻しましょう。
スレッドは理解している人からすると、簡単に扱えるし効率的に使用することができます。
しかし、初心者の方(特にクリスマスイブに予定がない方)からすると「スレッド 扱い わからない [検索]」はあるあるだと思うのでそういった方向けです。

ちなみに、このくだりは明日のiOS Advent Calendar 2014の記事でも使う予定です。乞うご期待(?)


UIのカクツキ、A・N・R!

皆さんはアプリを作っていているとUIのカクツキにいつかはぶち当たるでしょう。

そして突然現れる A・N・R!

_人人人人人人人人人人人人人人人人人人人人人人人人人人_
>  Application "Christmas eve" is not responding.  <
 ̄YYYYYYYYYYYYYYYYYYYYYYYYYY ̄

※クリスマス(イブ)に予定が無いのでテンション高めです。

ANRが発生するアプリケーションはツライ!

「これぞAndroid!!」とAndroidユーザーは泣いて喜んでいた時代もありますが、最近の端末はスペックが高くなってきたので、ANRが頻発するアプリはだんだんと少なくなっています。

そんな中、まだANRが発生しているとレビューで罵倒され兼ねないので気をつけましょう。

Implements Runnable then new Thread(task) to start

「自前で実装している処理が重い!」

そんなときのケアは自分で行わないといけません。

重い処理をRunnableを実装した別クラスで定義し、オーバーライドしたrunメソッドにその処理を定義します。

そのクラスのインスタンスを生成し、スレッドに渡して処理をUIスレッドと分けます。

public class Task implements Runnable {

    @Override
    public void run() {
        // Default setting
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DEFAULT);

        for (int i = 0; i < 100; i++) {
            LogUtils.th(this.getClass(), "Loop: " + i);
        }
    }

}

今回はTaskというクラスを実装しました。それを違うスレッドで処理するにはThreadクラスを使います。簡単ですね。

(new Thread(new Task())).start();

これだけで別スレッドでの処理が実現できます。

ただし、UIに関する処理は 必ず UIスレッド(メインスレッド)で行ってください。

※必ずしも、Runnableクラスを別で実装する必要はありません。Activity自身で定義しても良いですし、無名内部クラスで定義してもいいです。

ThreadPoolExecutor

実際、非同期な処理は先ほどの実装か、IntentServiceAsyncTaskだけで大半は済むかと思います。

ですが、タスクを何回かの複数のスレッド上で行いたい場合、しっかりとスレッドを管理してあげないと意図しない挙動となったり、ANRが発生することもあります。

では、そのスレッドを管理するマネージャークラスを自前しますか……Noですね。今の時代はなんでも揃っています。

JavaにはExecutorServiceクラスのサブクラスに当たるThreadPoolExecutorというものが存在します。

これを使って簡単に効率的でスレッドセーフなマルチスレッド処理を実装することができます。

まず、スレッドを管理するクラスとしてTaskQueueというシングルトンクラスを作ります。

シングルトンの生成はこのクラス内部だけにし、他のクラスからは生成できないようにしてください。

そして、内部にpoolとしてThreadPoolExecutorを定義します。

public class TaskQueue {

    // A managed pool of threads
    private final ThreadPoolExecutor mTaskThreadPool;

    // Gets the number of available cores
    private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
    // Sets the amount of time an idle thread waits before terminating
    private static final int KEEP_ALIVE_TIME = 1;
    // Sets the Time Unit to seconds
    private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;

    // A queue of Runnables for the pool
    private final BlockingQueue<Runnable> mPoolWorkQueue;

    private static TaskQueue sInstance = null;
    static {
        sInstance = new TaskQueue();
    }

    private TaskQueue() {

        LogUtils.th(this.getClass(), "Number of Cores => " + NUMBER_OF_CORES);

        mPoolWorkQueue = new LinkedBlockingQueue<Runnable>();

        // Creates a thread pool manager
        mTaskThreadPool = new ThreadPoolExecutor(
                NUMBER_OF_CORES,
                NUMBER_OF_CORES,
                KEEP_ALIVE_TIME,
                KEEP_ALIVE_TIME_UNIT,
                mPoolWorkQueue);
    }

    /**
     * Returns the TaskQueue object
     * @return The global TaskQueue object
     */
    public static TaskQueue getInstance() {
        return sInstance;
    }

    public void addTask(Runnable r) {
        sInstance.mTaskThreadPool.execute(r);
    }

}

このクラスはシンプルに実装してあり、インスタンスの生成時にThreadPoolExecutorを一緒に作り上げています。

そして、つくったPoolにaddTaskメソッドでタスクを追加していきます。

ThreadPoolExecuterに設定するパラメータ

ThreadPoolExecuterに設定するパラメータの説明です。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

int corePoolSize, int maximumPoolSize

ここにはスレッドプールの最小と最大をセットします。

設定する値は アクティブ になっている端末のCPUのコア数がベターです。

// Gets the number of available cores
private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();

CPUのコアは場合によってはアクティブでないものも存在するため、ここは固定値とはなりません。気をつけて下さい。

long keepAliveTime

スレッドがターミネートされるまでのアイドル時間

// Sets the amount of time an idle thread waits before terminating
private static final int KEEP_ALIVE_TIME = 1;

TimeUnit unit

keepAliveTimeで設定した時間の単位

// Sets the Time Unit to seconds
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;

BlockingQueue workQueue

Poolが各タスクを保持するためのキューです

// A queue of Runnables for the pool
private final BlockingQueue<Runnable> mPoolWorkQueue;
// ...
mPoolWorkQueue = new LinkedBlockingQueue<Runnable>();

使い方

必要なところでaddTaskメソッドにタスクを突っ込みます。

for (int i = 0; i < 10; i++) {
    TaskQueue.getInstance().addTask(new Task());
}

おわりに

今回のサンプルプロジェクトのリポジトリはこちらです。

https://github.com/kaneshin/Thread-Test-Android

今回はスレッドに関してで、UIスレッドには触れていません。

ですが、UIスレッドとの連携も考える必要はあります。また、今度時間があればいろいろと紹介します。

明日、僕が担当するiOS Advent Calendar 2014の記事もよろしくお願いします。書くのはスレッドについてです。(またかよ)

この投稿は Android Advent Calendar 201424日目の記事です。