The Android Developer Challenge is back! Submit your idea before December 2.

複数のスレッド用のマネージャーの作成

前回のガイドでは、スレッドで実行されるコードを指定する方法を説明し、異なるスレッドで実行されるタスクを定義する方法を示しました。タスクを 1 回だけ実行したい場合は、これで十分です。さまざまなデータセットでタスクを繰り返し実行したいが、一度に実行する必要があるのは 1 つだけである場合、IntentService がニーズに合っています。リソースが使用可能になったときにタスクを自動的に実行したり、複数のタスクを同時に(または両方)実行できるようにしたりするには、管理されたスレッドのコレクションを用意する必要があります。これを行うには、ThreadPoolExecutor インスタンスを使用します。このインスタンスは、スレッドプール内のスレッドが解放されたときにキューからタスクを実行します。タスクを実行するには、そのタスクをキューに追加するだけです。

スレッドプールはタスクの複数の並列インスタンスを実行できるため、コードがスレッドセーフであることを確認する必要があります。複数のスレッドがアクセスできる変数を synchronized ブロックで囲みます。このアプローチにより、1 つのスレッドが変数を読み取り中に別のスレッドがその変数に書き込むことがないようにします。通常、この状況は静的変数で発生しますが、一度だけインスタンス化されるどのオブジェクトでも発生します。詳細については、プロセスとスレッドの概要ガイドをご覧ください。

スレッドプール クラスを定義する

ThreadPoolExecutor を独自のクラスでインスタンス化します。このクラス内で、次の手順を実行します。

スレッドプールに静的変数を使用する
制限付き CPU またはネットワーク リソース用の単一のコントロール ポイントを保持するために、アプリのスレッドプールの単一インスタンスのみが必要な場合があります。Runnable タイプが複数あると、それぞれにスレッドプールが必要な場合がありますが、これらはそれぞれ単一のインスタンスにできます。たとえば、これをグローバル フィールド宣言の一部として追加できます(Kotlin の場合は、オブジェクトを作成します)。

Kotlin

    // Creates a single static instance of PhotoManager
    object PhotoManager {
        ...
    }
    

Java

    public class PhotoManager {
        ...
        static  {
            ...
            // Creates a single static instance of PhotoManager
            sInstance = new PhotoManager();
        }
        ...
    
private コンストラクタを使用する
コンストラクタを private にすると、そのコンストラクタがシングルトンになることが保証されます。つまり、クラスへのアクセスを synchronized ブロックで囲む必要がありません(Kotlin の場合、これはオブジェクトとして定義され、1 回だけしか初期化されないため、private コンストラクタは必要ありません)。

Kotlin

    object PhotoManager {
        ...
    }
    

Java

    public class PhotoManager {
        ...
        /**
         * Constructs the work queues and thread pools used to download
         * and decode images. Because the constructor is marked private,
         * it's unavailable to other classes, even in the same package.
         */
        private PhotoManager() {
        ...
        }
    
スレッドプール クラスのメソッドを呼び出して、タスクを開始します。
スレッドプールのキューにタスクを追加するスレッドプール クラスでメソッドを定義します。次に例を示します。

Kotlin

    object PhotoManager {
        ...
        fun startDownload(imageView: PhotoView, downloadTask: DownloadTask, cacheFlag: Boolean) =
                decodeThreadPool.execute(downloadTask.getHTTPDownloadRunnable())
        ...
    }
    

Java

    public class PhotoManager {
        ...
        // Called by the PhotoView to get a photo
        static public PhotoTask startDownload(
            PhotoView imageView,
            DownloadTask downloadTask,
            boolean cacheFlag) {
            ...
            // Adds a download task to the thread pool for execution
            sInstance.
                    downloadThreadPool.
                    execute(downloadTask.getHTTPDownloadRunnable());
            ...
        }
    
コンストラクタで Handler をインスタンス化し、アプリの UI スレッドにアタッチします。
Handler を使用すると、アプリで View オブジェクトなどの UI オブジェクトのメソッドを安全に呼び出すことができます。ほとんどの UI オブジェクトは、UI スレッドからのみ安全に変更できます。このアプローチについては、UI スレッドと通信するのレッスンで詳しく説明します。次に例を示します。

Kotlin

    object PhotoManager {
        ...
        private val handler = object : Handler(Looper.getMainLooper()) {

            /*
             * handleMessage() defines the operations to perform when
             * the Handler receives a new Message to process.
             */
            override fun handleMessage(msg: Message?) {
                ...
            }
            ...
        }
    }
    

Java

        private PhotoManager() {
        ...
            // Defines a Handler object that's attached to the UI thread
            handler = new Handler(Looper.getMainLooper()) {
                /*
                 * handleMessage() defines the operations to perform when
                 * the Handler receives a new Message to process.
                 */
                @Override
                public void handleMessage(Message inputMessage) {
                    ...
                }
            ...
            }
        }
    

スレッドプール パラメータを特定する

全体的なクラス構造ができたら、スレッドプールの定義を開始できます。ThreadPoolExecutor オブジェクトをインスタンス化するには、次の値が必要です。

初期プールサイズと最大プールサイズ
プールに割り当てるスレッドの初期数と最大許容数。スレッドプールに含めることができるスレッドの数は、主にデバイスで使用可能なコアの数に依存します。コアの数はシステム環境から入手できます。

Kotlin

    object PhotoManager {
        ...
        /*
         * Gets the number of available cores
         * (not always the same as the maximum number of cores)
         */
        private val NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors()
    }
    

Java

    public class PhotoManager {
    ...
        /*
         * Gets the number of available cores
         * (not always the same as the maximum number of cores)
         */
        private static int NUMBER_OF_CORES =
                Runtime.getRuntime().availableProcessors();
    }
    
この数は、デバイス内の物理コアの数を反映していない場合があります。一部のデバイスには、システムの負荷に応じて 1 つ以上のコアを非アクティブ化する CPU が搭載されています。このようなデバイスの場合、availableProcessors()アクティブなコアの数を返しますが、これはコアの総数より少ない場合があります。
キープアライブの時間と時間単位
スレッドがシャットダウンするまでアイドル状態のままでいる時間。この時間は、TimeUnit で定義された定数の 1 つである時間単位の値によって解釈されます。
タスクのキュー
ThreadPoolExecutorRunnable オブジェクトを取得する受信キュー。スレッドでコードを開始するために、スレッドプール マネージャーは先入れ先出しでキューから Runnable オブジェクトを取得し、スレッドにアタッチします。BlockingQueue インターフェースを実装するキュークラスを使用して、スレッドプールを作成するときに、このキュー オブジェクトを提供します。アプリの要件に合わせて、利用可能なキューの実装を選択できます。詳細については、ThreadPoolExecutor クラスの概要をご覧ください。次の例では、LinkedBlockingQueue クラスを使用します。

Kotlin

    object PhotoManager {
        ...
        // Instantiates the queue of Runnables as a LinkedBlockingQueue
        private val decodeWorkQueue: BlockingQueue<Runnable> = LinkedBlockingQueue<Runnable>()
        ...
    }
    

Java

    public class PhotoManager {
        ...
        private PhotoManager() {
            ...
            // A queue of Runnables
            private final BlockingQueue<Runnable> decodeWorkQueue;
            ...
            // Instantiates the queue of Runnables as a LinkedBlockingQueue
            decodeWorkQueue = new LinkedBlockingQueue<Runnable>();
            ...
        }
        ...
    }
    

スレッドプールを作成する

スレッドのプールを作成するには、ThreadPoolExecutor() を呼び出してスレッドプール マネージャーをインスタンス化します。これにより、制限付きのスレッドのグループが作成、管理されます。初期プールサイズと最大プールサイズは同じであるため、ThreadPoolExecutor はインスタンス化されたときにすべてのスレッド オブジェクトを作成します。次に例を示します。

Kotlin

    object PhotoManager {
        ...
        // Sets the amount of time an idle thread waits before terminating
        private const val KEEP_ALIVE_TIME = 1L
        // Sets the Time Unit to seconds
        private val KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS
        // Creates a thread pool manager
        private val decodeThreadPool: ThreadPoolExecutor = ThreadPoolExecutor(
                NUMBER_OF_CORES,       // Initial pool size
                NUMBER_OF_CORES,       // Max pool size
                KEEP_ALIVE_TIME,
                KEEP_ALIVE_TIME_UNIT,
                decodeWorkQueue
        )
    

Java

        private PhotoManager() {
            ...
            // 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;
            // Creates a thread pool manager
            decodeThreadPool = new ThreadPoolExecutor(
                    NUMBER_OF_CORES,       // Initial pool size
                    NUMBER_OF_CORES,       // Max pool size
                    KEEP_ALIVE_TIME,
                    KEEP_ALIVE_TIME_UNIT,
                    decodeWorkQueue);
        }
    

また、スレッドプールのサイズ設定を詳細に管理したくない場合は、単一スレッド エグゼキュータまたはワーク スティーリング エグゼキュータを作成するための Executors ファクトリ メソッドの方が便利な場合があります。

詳細

Android でのマルチスレッド操作の詳細については、プロセスとスレッドの概要ガイドをご覧ください。

サンプルアプリ

このガイドのコンセプトを試すには、ThreadSample をダウンロードします。