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

この記事は[Android Advent Calendar 2014](http://qiita.com/advent-calendar/2014/android)の24日目の記事です。

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


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

----

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

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

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

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


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


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

ちなみに、このくだりは明日の[iOS Advent Calendar 2014](http://qiita.com/advent-calendar/2014/ios)の記事でも使う予定です。乞うご期待（？）

----

## UIのカクツキ、Ａ・Ｎ・Ｒ！

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

そして突然現れる **Ａ・Ｎ・Ｒ！**

```
＿人人人人人人人人人人人人人人人人人人人人人人人人人人＿
＞  Application "Christmas eve" is not responding.  ＜
￣ＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹＹ￣
```

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

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

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

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


## Implements `Runnable` then `new Thread(task)` to start

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

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

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

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

```java
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クラスを使います。簡単ですね。

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

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

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

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


## ThreadPoolExecutor

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

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

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


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

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


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

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

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

```java
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`に設定するパラメータの説明です。

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

 **int corePoolSize, int maximumPoolSize**

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

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

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

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


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

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

 
 **TimeUnit unit**
 
`keepAliveTime`で設定した時間の単位

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


 **BlockingQueue<Runnable> workQueue**

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

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

### 使い方

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

```java
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](http://qiita.com/advent-calendar/2014/ios)の記事もよろしくお願いします。書くのはスレッドについてです。（またかよ）