web-dev-qa-db-ja.com

new Thread(task).start()VS ThreadPoolExecutor.submit(task)in Android

私のAndroidプロジェクトでは、コードを非同期で実行する必要がある場所がたくさんありました(Webリクエスト、dbの呼び出しなど)。これは長時間実行されるタスクではありません(最大数秒)。 )これまで、私は新しいスレッドを作成し、タスクで新しい実行可能ファイルを渡すことでこの種のことを行っていました。しかし最近、スレッドと並行性に関する記事をJavaで読み、理解しました。すべてのタスクに対して新しいスレッドを作成することは良い決断ではありません。

これで、5つのスレッドを保持するThreadPoolExecutorクラスにApplicationを作成しました。コードは次のとおりです。

public class App extends Application {

    private ThreadPoolExecutor mPool;

    @Override
    public void onCreate() {
        super.onCreate();

        mPool =  (ThreadPoolExecutor)Executors.newFixedThreadPool(5);
    }
}

また、Runnableタスクをエグゼキュータに送信するメソッドがあります。

public void submitRunnableTask(Runnable task){
    if(!mPool.isShutdown() && mPool.getActiveCount() != mPool.getMaximumPoolSize()){
        mPool.submit(task);
    } else {
        new Thread(task).start();
    }
}

したがって、コードで非同期タスクを実行する場合は、Appのインスタンスを取得し、submitRunnableTaskメソッドを呼び出してランナブルを渡します。ご覧のとおり、スレッドプールにタスクを実行するための空きスレッドがあるかどうかも確認します。ない場合は、新しいスレッドを作成します(これが発生するとは思いませんが、いずれにせよ...私はしません。タスクをキューで待機させてアプリの速度を低下させたい)。

アプリケーションのonTerminateコールバックメソッドで、プールをシャットダウンしました。

だから私の質問は次のとおりです:この種のパターンは、コードで新しいスレッドを作成するよりも優れていますか?私の新しいアプローチにはどのような長所と短所がありますか?まだ気付いていない問題が発生する可能性はありますか?非同期タスクを管理するために、これよりも優れたアドバイスをいただけますか?

P.S. AndroidとJavaの経験はありますが、並行性の第一人者にはほど遠いです)ですから、この種の質問ではよく理解できない側面があるかもしれません。アドバイスをいただければ幸いです。

15
Andranik

この回答はあなたのタスクが短いことを前提としています

この種のパターンは、コードで新しいスレッドを作成するよりも優れていますか?

それは良いですが、それでも理想からは程遠いです。あなたはまだ短いタスクのスレッドを作成しています。代わりに、たとえばExecutors.newScheduledThreadPool(int corePoolSize)によって別のタイプのスレッドプールを作成する必要があります。

行動の違いは何ですか?

  • FixedThreadPoolには常に使用するスレッドのセットがあり、すべてのスレッドがビジーの場合、新しいタスクがキューに入れられます。
  • ScheduledThreadPoolクラスによって作成された(デフォルトの)Executorsには、minimumスレッドプールがあり、アイドル状態でも。新しいタスクが入ったときにすべてのスレッドがビジーの場合、そのスレッドの新しいスレッドを作成し、完了してから60秒後にスレッドを破棄します。再び必要にならない限り。

2つ目は、自分で新しいスレッドを作成しないようにすることができます。この動作は「スケジュール済み」の部分がなくても実現できますが、エグゼキュータを自分で構築する必要があります。コンストラクターは

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

さまざまなオプションを使用して、動作を微調整できます。

一部のタスクが長い場合...

そして、私は長いことを意味します。ほとんどのアプリケーションライフタイムと同様(リアルタイム双方向接続?サーバーポート?マルチキャストリスナー?)。その場合、Runnableをエグゼキュータに入れることは有害です-標準のエグゼキュータはそれに対処するように設計されていません、そしてそれらのパフォーマンスは劣化します。

固定スレッドプールについて考えてみてください。実行時間の長いタスクが5つある場合、新しいタスクは新しいスレッドを生成し、プールの可能なゲインを完全に破壊します。より柔軟なエグゼキュータを使用する場合-一部のスレッドは共有されますが、常にではありません。

経験則は

  • 短いタスクの場合-エグゼキュータを使用します。
  • 長いタスクの場合-エグゼキュータがそれを処理できることを確認します(つまり、最大プールサイズがないか、処理するのに十分な最大スレッドがありませんもう1つのスレッドがしばらくの間なくなっています)
  • 常にメインスレッドと一緒に実行する必要がある並列プロセスの場合は、別のスレッドを使用してください。
20
Ordous

質問に答えるには—はい、次の理由により、Executorを使用する方が新しいスレッドを作成するよりも優れています。

  1. Executorは、さまざまなスレッドプールの選択肢を提供します。スレッドの作成はコストのかかる操作であるため、既存のスレッドを再利用できるため、パフォーマンスが向上します。
  2. スレッドが停止した場合、Executorはアプリケーションに影響を与えることなくスレッドを新しいスレッドに置き換えることができます。
  3. マルチスレッドポリシーへの変更は、Executor実装のみを変更する必要があるため、はるかに簡単です。
6
Tanay

Ordousのコメントに基づいて、1つのプールのみで機能するようにコードを変更しました。

public class App extends Application {

    private ThreadPoolExecutor mPool;

    @Override
    public void onCreate() {
        super.onCreate();

        mPool =  new ThreadPoolExecutor(5, Integer.MAX_VALUE, 1, TimeUnit.MINUTES, new SynchronousQueue<Runnable>());
    }
}


public void submitRunnableTask(Runnable task){
    if(!mPool.isShutdown() && mPool.getActiveCount() != mPool.getMaximumPoolSize()){
        mPool.submit(task);
    } else {
        new Thread(task).start(); // Actually this should never happen, just in case...
    }
}

ですから、これが他の誰かに役立つことを願っています。経験豊富な人々が私のアプローチについてコメントをいただければ、彼らのコメントに感謝します。

4
Andranik