Androidスレッドプールの設計原則

プロセス:各アプリが実行される前にプロセスが最初に作成されます。このプロセスはZygoteによって分岐され、アプリで実行されているさまざまなアクティビティ/サービスやその他のコンポーネントをホストするために使用されます。このプロセスは、上位層のアプリケーションに対して完全に透過的です。これは、AndroidランタイムでAppプログラムを実行することもGoogleの意図です。ほとんどの場合、AndroidManifest.xmlでAndroid:process属性が構成されていないか、プロセスがネイティブコードを通じてフォークされていない限り、アプリはプロセスで実行されます。  

スレッド:スレッドはアプリケーションに非常に一般的です。たとえば、新しい各スレッド()。Startは新しいスレッドを作成します。スレッドとアプリが配置されているプロセスの間のリソース共有。Linuxの観点からは、プロセスとスレッドの間にリソースを共有するかどうかを除いて、本質的な違いはありません。これらはすべてtask_struct構造体です。CPUの観点からは、プロセスまたはスレッドは実行可能ファイルにすぎません。コードでは、CPUはCFSスケジューリングアルゴリズムを使用して、各タスクがCPUタイムスライスを可能な限り公平に享受できるようにします。


1.スレッドプールについて 

Androidのスレッドプールの概念は、JavaのExecutorに由来し、それらの使用法は基本的に同じです。エグゼキュータはインターフェイスであり、実際のスレッドプールの実装はThreadPoolExecutorです。ThreadPoolExecutorは、スレッドプールを構成するための一連のパラメーターを提供します。Androidで一般的に使用されるいくつかのスレッドプールは、ThreadPoolExecutorのさまざまな構成によって実装されます。

ThreadPoolExecutorコンストラクター

ThreadPoolExecutorには複数のオーバーロードメソッドがありますが、最終的にはこのコンストラクターが呼び出されます。

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                    BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)
复制代码

corePoolSize:スレッドプール内のコアスレッドの数。 

maximumPoolSize:スレッドプール内のスレッドの最大数。  

keepAliveTime:非コアスレッドのタイムアウト期間システム内の非コアスレッドのアイドル時間がkeepAliveTimeを超えると、リサイクルされます。ThreadPoolExecutorのallowCoreThreadTimeOut属性がtrueに設定されている場合、このパラメーターはコアスレッドのタイムアウト期間も示します。  

unit:keepAliveTimeパラメータの単位。ナノ秒、マイクロ秒、ミリ秒、秒、分、時間、日などがあります。

 workQueue:スレッドプール内のタスクキュー。このキューは主に、送信されたがまだ実行されていないタスクを格納するために使用されます。ここに格納されているタスクは、ThreadPoolExecutorのexecuteメソッドによって送信されます。 

threadFactory:スレッドプールの新しいスレッドを作成する機能を提供します。通常はデフォルトを使用します。

 ハンドラー:拒否戦略、スレッドが新しいタスクを実行できない場合(通常、スレッドプール内のスレッド数が最大数に達したか、スレッドプールが閉じられているため)、デフォルトでは、スレッドプールが新しいスレッドを処理できない場合、それがスローされますRejectedExecutionException。 

2つの実行方法

ThreadPoolExecutorには、submit()とexecute()の2つの実行メソッドがあります。まず、これら2つのメソッドの違いを見てみましょう。

execute()メソッドのソースコード:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //获得当前线程的生命周期对应的二进制状态码
        int c = ctl.get();
        //判断当前线程数量是否小于核心线程数量,如果小于就直接创建核心线程执行任务,创建成功直接跳出,失败则接着往下走.
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //判断线程池是否为RUNNING状态,并且将任务添加至队列中.
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //审核下线程池的状态,如果不是RUNNING状态,直接移除队列中
            if (! isRunning(recheck) && remove(command))
                reject(command);
                //如果当前线程数量为0,则单独创建线程,而不指定任务.
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果不满足上述条件,尝试创建一个非核心线程来执行任务,如果创建失败,调用reject()方法.
        else if (!addWorker(command, false))
            reject(command);
    }
复制代码

送信()メソッドのソースコード:

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        //还是通过调用execute
        execute(ftask);
        //最后会将包装好的Runable返回
        return ftask;
    }

//将Callable<T> 包装进FutureTask中
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

//可以看出FutureTask也是实现Runnable接口,因为RunableFuture本身就继承了Runnabel接口
public class FutureTask<V> implements RunnableFuture<V> {
    .......
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}
复制代码

上記の2つのメソッドのソースコードから、いくつかの結論を分析できます。実際、submit()は、タスクを実行するためにexecute()を呼び出す必要があります。違いは、submit()がパッケージ化されたタスクを返し、Futureオブジェクトを返すことです。 。execute()メソッドから、addWorker()メソッドがスレッド(コアスレッド、非コアスレッド)を作成するためのメインメソッドであり、reject()メソッドがスレッドの失敗に対するコールバックを作成することを確認することは難しくありません。したがって、通常の状況では、結果の値を返すためにスレッドを実行する必要がない場合は、executeメソッドを使用します。また、値を返す必要がある場合は、submitメソッドを使用すると、Futureオブジェクトが返されます。Futureは結果を取得できるだけでなく、キャンセルすることもできます。futureのcancel()メソッドを呼び出すことにより、Futureの実行をキャンセルできます。たとえば、スレッドを追加しましたが、それを中断したいプロセスでは、sumbitを介して達成できます。

次に、スレッドプールを使用する利点は何ですか。

 1.頻繁なスレッドの作成と破棄を避けます。スレッドを使用してスレッドを作成すると、時間のかかる操作を実行できますが、多数のスレッドが作成および破棄されると、過度のパフォーマンスオーバーヘッドが発生します。

 2.システムリソースに負担をかけないようにします。多数のスレッドが一緒に動作すると、リソースの緊張が生じる可能性があります。スレッドの基礎となるメカニズムもCPU時間に分割されます。同時に多数のスレッドが存在する場合、それらが互いにリソースを横取りし、結果としてブロックする可能性があります現象。  

3.スレッド管理の向上ダウンロード機能を例にとると、通常の状況では、同時ダウンロードの最大数に制限があり、スレッドプールを使用して、同時ダウンロードの最大数、ダウンロードタスクシーケンスのシリアル実行、および実際のニーズに応じて待機するキューを柔軟に設定できます。 

3つ、Androidで一般的に使用されるいくつかのスレッドプール

3.1 FixedThreadPool

そのソースコードは次のとおりです。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
复制代码

ソースコードから2つの特性を確認できます。 

 1.着信パラメーターが1つ、つまりコアスレッドの数が固定されており、外部着信に対して1つのnThreadsのみが提供され、そのコアスレッド番号は最大スレッド数と同じである。これは、FixedThreadPoolに非コアスレッドがなく、すべてのスレッドがコアスレッドであることを示しています。 

 2.スレッドのタイムアウト期間は0です。これは、実行するタスクがない場合でもコアスレッドが破棄されないことを示しています。これにより、FixedThreadPoolが要求にすばやく応答できるようになります。最後のスレッドキューはLinkedBlockingQueueですが、LinkedBlockingQueueにはパラメーターがありません。これは、スレッドキューのサイズがInteger.MAX_VALUE(2 ^ 31-1)であることを示しています。上記のソースコードパラメーターから、FixedThreadPoolの動作特性の一般的な理解も必要です他のいくつかのスレッドプールを引き続き分析します。 

 3.2 SingleThreadExecutor 

そのソースコードは次のとおりです。

public static ExecutorService newSingleThreadExecutor() {  
    return new FinalizableDelegatedExecutorService  
        (new ThreadPoolExecutor(1, 1,  
                                0L, TimeUnit.MILLISECONDS,  
                                new LinkedBlockingQueue<Runnable>()));  
复制代码

ソースコードから、SingleThreadExecutorがFixedThreadPoolに類似していることが簡単にわかります。違いは、SingleThreadExecutorコアスレッドの数が1つしかないことです。つまり、同時に実行できるスレッドは1つだけです。スレッドの同期の問題に対処するのを避けるために使用します。類推を使うと、チケットを購入するためのキューイングに似ており、一度に1人の業務しか処理できません。実際、FixedThreadPoolのパラメーターを1として渡すと、SingleThreadExecutorと同じになります。

3.3 CachedThreadPool

そのソースコードは次のとおりです。

   public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
复制代码

ソースコードからわかるように、

CachedThreadPoolにはコアスレッドはありませんが、スレッドの最大数はInteger.MAX_VALUEです。さらに、CachedThreadPoolにはスレッドタイムアウトメカニズムがあり、タイムアウト期間は60秒です。スレッドの最大数は無限であるため、新しいタスクが追加されるたびに、スレッドプールにアイドルスレッドがある場合、アイドルスレッドは新しいタスクを実行します。アイドルスレッドがない場合、タスクを実行するために新しいスレッドが作成されます。CachedThreadPoolの特性によると、CachedThreadPoolには、時間のかかるタスクリクエストが多数ある場合に使用できます。これは、CachedThreadPoolに新しいタスクがない場合、60秒のタイムアウトにより、その中のすべてのスレッドが終了するためです。

3.4 ScheduledThreadPool

そのソースコードは次のとおりです。

public ScheduledThreadPoolExecutor(int corePoolSize) {  
    super(corePoolSize, Integer.MAX_VALUE,  
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,  
          new DelayedWorkQueue());  
} 
复制代码

ソースコードから、コアスレッドの数は固定されていますが、非コアスレッドは無限であることがわかります。非コアスレッドがアイドル状態の場合は、すぐにリサイクルされます。
また、ScheduledThreadPoolは、4つのスレッドプールの中で、タスクを定期的に実行する機能を持つ唯一のプールです。定期的なタスクや遅延タスクの実行に適しています。

第4に、スレッドプール内のスレッドタスクを終了する方法は?

java.lang.Threadクラスには、start()、stop()、stop(Throwable)、suspend()、destroy()、resume()などの一般的に使用されるメソッドが含まれています。これらのメソッドにより、スレッドに対して便利な操作を実行できますが、これらのメソッドの中start()で保持されるのメソッドのみです。

《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?》これらのメソッドを中止する理由  は、JDKのヘルプドキュメントとSunの記事で説明されています。

  単純な理由は、stopメソッドを使用して强行终止スレッド実行または中断する危险ことができますが、通常の手順に従ってシャットダウンするのではなく、コンピューターの電源を突然シャットダウンするのと同じように、stopメソッドを使用すると非常に便利です。したがって、stopメソッドを使用してスレッドを終了することはお勧めしません。

 では、スレッドをどのように停止すればよいでしょうか?

  • 1.タスクには通常ループ構造があり、ループを制御するためにマークを使用する限り、タスクを終了することができます。
  • 2.スレッドが入って冻结状态おり、マークを読み取れない場合は、interrupt()メソッドを使用し从冻结状态强制恢复到运行状态中てスレッドを呼び出し、CPU実行に適格なスレッドにすることができます

一般的なスレッドがrunメソッドを実行した後、スレッドは正常に終了するため、実装する方法はいくつかあります。

4.1 futureおよびcallableの使用

手順:

  1. Callableインターフェースを実装する
  2. pool.submit()メソッドを呼び出して、Futureオブジェクトを返します。
  3. スレッドの状態を取得するには、Futureオブジェクトを使用します。

private void cancelAThread() {
        ExecutorService pool = Executors.newFixedThreadPool(2);
          
          Callable<String> callable = new Callable<String>() {
              
            @Override
            public String call() throws Exception {
                System.out.println("test");
                return "true";
            }
        };
          
        Future<String> f = pool.submit(callable);
          
        System.out.println(f.isCancelled());
        System.out.println(f.isDone());
        f.cancel(true);
  
    }
复制代码

【好奇心】

(1)future.cancel(mayInterruptIfRunning)の内部実装はどのようになりますか?スレッドプールで実行されている「その」タスクを中断できます。

特定のスレッドIDを記録する必要があり、割り込み信号が送信されていると推測できます。

(2)推測してください。それは、ブロック内の操作を中断するための割り込み信号を送信するだけです。while(true)の場合、CPUを占有するこの非ブロッキング操作は中断できません。つまり、スレッドはまだ実行中であり、スレッドプールリソースを占有しています。

【ご注意】

a)。スレッドプールリソースが制限されており、一部のタスクが送信()されず、例外がスローされます:java.util.concurrent.RejectedExecutionException

b)submit()が成功する限り、スレッドが実行中か、BlockingQueueでの実行を待機しているかに関係なく、future.cancel()オペレーションはスレッドに割り込むことができます。つまり、実際の実行とは何の関係もなく、実行がブロックされるか、実行のスケジュールが設定されるのを待っている間に中断されます。

4.2 volatileキーワードの使用

スレッドを終了するには、終了フラグを設定します。

public class ThreadSafe extends Thread {
    public volatile boolean isCancel = false; 
        public void run() { 
        while (!isCancel){
           //TODO method(); 
        }
    } 
}

复制代码

4.3 interrupt()メソッドの使用

スレッドを終了し、例外をキャッチします。

Thread.interrupt()メソッドは、スレッドに割り込みます。スレッドの割り込みステータスビットが設定、つまりtrueに設定されます。割り込みの結果は、プログラム自体に応じて、スレッドが終了するか、新しいタスクを待つか、次のステップに進むかです。スレッドは、この割り込みフラグを時々チェックして、スレッドを割り込みする必要があるかどうか(割り込みフラグの値がtrueかどうか)を判断します。停止メソッドのように実行中のスレッドを中断しません 

public class ThreadSafe extends Thread {
  
   @Override
    public void run() { 
        while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
            try{
              //TODO method(); 
              //阻塞过程捕获中断异常来退出
            }catch(InterruptedException e){
                e.printStackTrace();
                break;//捕获到异常之后,执行break跳出循环。
            }
        }
    } 
}复制代码

  interrupt()メソッドは、割り込みステータスを変更するだけで、実行中のスレッドを中断しません。ユーザーはスレッドのステータスを監視し、自分で処理を行う必要があります。スレッドの割り込みをサポートする方法(つまり、スレッドの割り込み後にinterruptedExceptionをスローする方法)は、スレッドの割り込みステータスを監視することです。スレッドの割り込みステータスが「割り込みステータス」に設定されると、割り込み例外がスローされます。このメソッドが実際に行うことは、ブロックされたスレッドが割り込みフラグを検出してブロックされた状態を終了できるように、ブロックされたスレッドに割り込み信号を送信することです。

より具体的には、スレッドがObject.wait、Thread.join、Thread.sleepの3つのメソッドのいずれかによってブロックされ、このときにスレッドのinterrupt()メソッドが呼び出されると、スレッドはInterruptedException(スレッドブロックされた状態を早期に終了するために、この例外に対処するために事前に準備する必要があります。スレッドがブロックされていない場合、interrupt()を呼び出しても現時点では効果がなく、wait()、sleep()、join()が実行されるまで、InterruptedExceptionはすぐにはスローされません。

this.interrupted():現在のスレッドが中断されているかどうかをテストします(静的メソッド)。メソッドが継続的に呼び出される場合、2番目の呼び出しはfalseを返します。interrupted()メソッドには状態をクリアする機能があることが、APIドキュメントに記載されています。実行後、ステータスフラグをfalseにクリアする機能があります。

this.isInterrupted():スレッドが中断されたかどうかをテストしますが、ステータスフラグはクリアできません。


おすすめ

転載: juejin.im/post/5cf3adec51882502f94905d5