スレッドプールの使用 (Future/Callable と組み合わせて使用)

概要

スレッド プールの作成メソッドは常に共有 7 种 (その中には 6 种是通过 Executors 作成、1 种是通过ThreadPoolExecutor 作成) されますが、全体的には2 つのカテゴリに分類されます:

  1. スレッド プールはThreadPoolExecutor ; によって作成されました。
  2. Executors によって作成されたスレッド プール (以下では 4 つのタイプのみを説明します)。

7 つの作成メソッド
Java のスレッド プールに関連するクラスはすべて、jdk1.5 以降の java.util.concurrent パッケージに含まれています。いくつかのコア クラスとインターフェイスが含まれます。 Executor、Executor、ExecutorService、ThreadPoolExecutor、FutureTask、Callable、Runnable など。

エグゼキュータ/エグゼキュータサービス

Executor、Executors、ExecutorService、ThreadExecutorPool 間の関係、ブログ投稿
ここに画像の説明を挿入します

スレッド プールの作成とスレッドの使用に一般的に使用される Executor 実装では、主に以下のクラス図で提供されるクラスが使用されます。スレッド プールのクラス図は次のとおりです。
ここに画像の説明を挿入します
これには 3 つの Executor インターフェイスが含まれています。 :

  1. Executor: 新しいタスクを実行するためのシンプルなインターフェイス
  2. ExecutorService: Executor を拡張し、Executor のライフ サイクルとタスクのライフ サイクルを管理するためのメソッドを追加します。
  3. ScheduleExcutorService: ExecutorService を拡張して、将来および定期的な実行タスクをサポートします。

参考ブログ投稿

遺言執行者

多くのファクトリメソッドを提供し、以下のスレッドプールを作成するスレッドプールファクトリです。

// 创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor();
// 创建固定数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads);
// 创建带缓存的线程池
public static ExecutorService newCachedThreadPool();
// 创建定时调度的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
// 创建流式(fork-join)线程池,创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】
public static ExecutorService newWorkStealingPool();
//创建⼀个单线程的可以执⾏延迟任务的线程池;
Executors.newSingleThreadScheduledExecutor

Java は Executor を通じて 4 つのスレッド プールを提供します: スレッド プールを自動的に作成するいくつかの方法はすべて Executor ツール クラスにカプセル化されています

  1. newCachedThreadPoolキャッシュ可能なスレッド プールを作成します。スレッド プールの長さが処理の必要性を超えた場合、アイドル状態のスレッドを柔軟にリサイクルできます。リサイクルしない場合は、新しいスレッドを作成してください。
  2. newFixedThreadPool 同時スレッドの最大数を制御できる 固定長のスレッド プール を作成します。超過したスレッドはキューで待機します。
  3. newScheduledThreadPool スケジュールされた定期的なタスクの実行をサポートするために、 固定長のスレッド プール を作成します。
  4. newSingleThreadExecutorシングルスレッドのスレッド プールを作成します。これは、タスクの実行に一意のワーカー スレッドのみを使用し、すべてのタスクが指定された順序 (FIFO、LIFO、優先順位) で実行されるようにします。

スレッド プール コア クラス - ThreadPoolExecutor

ThreadPoolExecutor: スレッド プールを作成する最も独創的な方法で、設定する 7 つのパラメータが含まれています。

これはスレッド プールを手動で作成するために使用され、いくつかの構築方法が用意されていますが、一番下の構築方法は次の 1 つだけです。

/*
	corePoolSize:核心线程数,
		也是线程池中常驻的线程数,
		线程池初始化时默认是没有线程的,
		当任务来临时才开始创建线程去执行任务
	
	maximumPoolSize:最大线程数,
		在核心线程数的基础上可能会额外增加一些非核心线程,
		需要注意:只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
	
	keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,
		注意当corePoolSize=maxPoolSize时,
		keepAliveTime参数也就不起作用了(因为不存在非核心线程);
	
	unit:keepAliveTime的时间单位
	
	workQueue:用于保存任务的队列,
		等待队列,线程池中的线程数超过核心线程数时,任务将放在等待队列,
		它是一个BlockingQueue类型的对象
		可以为无界、有界、同步移交三种队列类型之一,
		当池子里的工作线程数大于corePoolSize时,
		这时新进来的任务会被放到队列中
	
	threadFactory:创建线程的工厂类,
		默认使用Executors.defaultThreadFactory(),
		也可以使用guava库的ThreadFactoryBuilder来创建
	
	handler:线程池无法继续接收任务
		队列已满且线程数达到maximunPoolSize)时的饱和策略
		取值有:
			AbortPolicy:中断抛出异常
			CallerRunsPolicy:默默丢弃任务,不进行任何通知
			DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
			DiscardPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)

*/
 public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    
    ……}

上記のパラメータで最も重要なのは、workQueue、threadFactory、および handler です。

待機キュー-workQueue

待機キューのタイプは BlockingQueue であり、理論的には、そのサブクラスである限り、待機キューとして使用できます。
以下は、jdk に組み込まれているブロック キューの一部です。

  1. ArrayBlockingQueue、キューは制限されており、配列に基づいて実装されたブロッキング キュー
  2. LinkedBlockingQueue の場合、キューは制限付きまたは制限なしにすることができます。リンクリストに基づくブロックキュー
  3. SynchronousQueue、要素を格納しないブロッキング キューでは、各挿入操作は、別のスレッドが削除操作を呼び出すまで待機する必要があります。そうしないと、挿入操作はブロックされたままになります。行列も、
  4. Executors.newCachedThreadPool() のデフォルト キュー
  5. PriorityBlockingQueue、優先度のある無制限のブロッキング キュー

スレッドファクトリー-スレッドファクトリー

ThreadFactory はメソッドが 1 つだけあるインターフェイスです

拒否ポリシーハンドラー

拒否戦略は、スレッド プールがいっぱいでキューもいっぱいの場合にタスクに対して実行するアクションです。

  1. AbortPolicy: 割り込みは例外をスローします
  2. CallerRunsPolicy: 通知なしでタスクをサイレントに破棄します。
  3. DiscardOldestPolicy: キュー内に最も長い時間存在していたタスクを破棄します。
  4. DiscardPolicy: タスクを送信したスレッドにタスクを実行させます (最初の 3 つと比較して、よりフレンドリーです)

4 つの戦略にはそれぞれ長所と短所があります。より一般的に使用されるのは DiscardPolicy です。ただし、この戦略の 1 つの欠点は、タスクの実行トラックが記録されないことです。したがって、多くの場合、RejectedExecutionHandler インターフェイスを実装してカスタム拒否戦略を実装する必要があります

FutureTask/実行可能

FutureTask は、キャンセル可能な非同期計算です。FutureTask は、Future の基本メソッドを実装します。
ここに画像の説明を挿入します
FutureTask クラスでよく使用されるメソッド:ブログ リファレンス 2

  1. public boolean isCancelled() このタスクが正常に完了する前にキャンセルされた場合は true を返します。
  2. public boolean isDone() タスクが完了した場合は true を返します。
  3. public V get() 計算が完了するまで待ってから結果を取得します。
  4. public V get(long timeout, TimeUnit unit) は、必要に応じて、計算が完了するまで一定時間待機してから、その結果を取得します (利用可能な場合)。
  5. public boolean cancel(boolean mayInterruptIfRunning)、このタスクをキャンセルしてみてください。
  6. protected void set(V v): この Future がすでに設定されているかキャンセルされていない限り、この Future の結果を指定された値に設定します。

FutureTask を作成する 2 つの方法:ブログ参照 1

  1. 呼び出し可能を使用する
     FutureTask<Boolean> future = new FutureTask<>(new Callable<Boolean>() {
          
          
       @Override
       public Boolean call() throws Exception {
          
          
         return true;
       }
     });
    
    
  2. 新しいFutureTaskを直接作成する
    //托管给线程池处理
    Future<?> futureResult = Executors.newCachedThreadPool().submit(future);
    //托管给单独线程处理
    //FutureTask继承了Runnable接口,
    //所以它可以通过new Thread()的方式进行运行,
    //再由future变量来检索结果值或者取消任务等操作,通过线程池托管的方式也可以适用。
    new Thread(future).start();
    
    

呼び出し可能

スレッドプール

参考ブログ投稿

新しいキャッシュされたスレッドプール

特徴:

  1. スレッド プール の数の上限は次のとおりです: Integer.MaxValue (2147483647);
  2. スレッド プールデフォルトのアイドル時間は 60 秒です。60 秒を超えると、スレッド プールから削除されます;
  3. 新来任务时,先检查是否有空闲线程可使用,若无,则创建一个新线程执行任务
  4. タスクは同期キューに保存されます。
  5. 适用场景:短时异步任务
    施工方法:
public static ExecutorService newCachedThreadPool() {
    
    
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

新しい固定スレッドプール

特徴:

  1. は、再利用可能な固定数のスレッドスレッド プールを作成するために使用されます。
  2. スレッド プール内のすべてのスレッドが実行されている場合、新しいスレッドはスレッド プールにアイドル状態のスレッドが現れるまで待機状態になります;
  3. タスクの途中でスレッドが終了して終了すると、新しいスレッドが代わりに未完了のタスクを完了し続けます。
  4. スレッドを閉じるために明示的なシャットダウン メソッドが使用されない限り、スレッドは常に存在し、リソースは解放されません。
  5. タスクは無制限のブロッキングキューに保存されます
  6. 适用场景:长期任务

施工方法:

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

新しいスケジュールされたスレッドプール

特徴:

  1. タスクは無制限の遅延キューに保存されます
  2. 該当するシーン:需要定期执行或延迟执行的任务

施工方法:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    
    
        return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    
    
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

newSingleThreadExecutor

特徴:

  1. 単一のワーカー スレッドを作成します。
  2. スレッドは順番に実行されます;
  3. タスクは無制限のブロッキングキューに保存されます
  4. 适用场景:需要按照顺序执行的任务

施工方法:

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

一般的に使用される方法:

public static void main(String args[]) 
{
    
    
    /*
     * 执行定时任务newScheduledThreadPool
     */
    ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
    //方法1:5秒后开始执行,每隔一秒执行一次
    /*
		scheduleAtFixedRate方法,一共四个参数,分别是:
			1.需要执行的任务task、
			2.延迟执行时间t1、
			3.每次执行任务的时间间隔t2、
			4.时间间隔单位。
		含义是:在t1时间过后,以 1次/t2 的频率来不断执行 task。
		代码中,在5秒延迟后,以 1次/1秒的频率执行 打印当前时间的任务。
	*/
    service.scheduleAtFixedRate(new ExecutorsTest2(), 5, 1, TimeUnit.SECONDS);
   
	
	//方法2:5秒后开始执行,每次任务执行完后延迟3秒后,继续执行下一次
	/*
		scheduleWithFixedDelay也是四个参数,分别是:
			1.待执行的任务Task,
			2.延迟时间t1,
			3.每次任务执行完毕后延迟t2秒后执行下次任务,
			4.延迟时间单位。
	*/
    service.scheduleWithFixedDelay(new ExecutorsTest3(), 5, 3, TimeUnit.SECONDS);
}

スレッドプールパラメータを構成する

タスクの特性に基づいて分析します。

  1. タスクの性質: CPU 集中型、IO 集中型、および混合
  2. タスクの優先度: 高、中、低
  3. タスクの実行時間: 長い、中程度、短い
  4. タスクの依存関係: データベースまたは他のシステム リソースに依存するかどうか

タスクが CPU を集中的に使用する場合は、スレッド プールの数を CPU の数に設定して、スレッドの切り替えによって発生するオーバーヘッドを軽減できます。
タスクが IO 集中型である場合は、スレッド プールの数をより大きく設定できます (CPU 数 * 2 など)。

可以通过Runtime.getRuntime().availableProcessors()来获取CPU的个数

Future/Callable と組み合わせて使用​​されます

メインスレッドは、タスクの実行方法を決定するために、子スレッドの実行結果を知る必要があります。JDK1.5 では、Callable と Future
< a i=2>, これらにより、タスクの実行完了後にタスクの実行結果を取得することができます。 Callable を使用する手順: 参考ブログ投稿

  1. タスク クラスは Callable インターフェイスを実装します。
  2. スレッド プールを作成します。 ExecutorService es = Executors.newCachedThreadPool();
  3. タスクを実行します: chuju cj = new chuju();Future future = es.submit(cj);
  4. 子スレッドのタスクの実行結果を取得します: future.get()

未来

FutureTask クラスは、Future インターフェイスの実装クラスであり、非同期タスク操作の特定の実装を提供します。

FutureTask クラスは、Future インターフェイスを実装するだけでなく、Runnable インターフェイスも実装します。より正確には、FutureTask クラスは RunnableFuture インターフェイスを実装します。

参考ブログ
ここに画像の説明を挿入します

時間のかかるタスクを実行する場合、別のスレッドを開始して、時間のかかるタスクを非同期に実行できます。同時に、他のことも実行できます。したがって、Future はマルチスレッドのアプリケーション モードでもあります 将来の「注文番号」に基づいて時間のかかるタスクの実行結果を抽出できます 。作業が完了すると、

Future の使用に関連する主な JDK クラスは、FutureTask と Callable です。

把任务提交给子线程去处理,主线程不用同步等待,当向线程池提交了一个Callable或Runnable任务时就会返回Future,用Future可以获取任务执行的返回结果

Future インターフェイスは、タスクの非同期実行の結果を象徴します。つまり、時間のかかるタスクの実行を別のスレッドで実行し、この時点で他のことを行うことができます。他のことを完了した後、Future を呼び出すことができます。 get() メソッドを使用して結果を取得します。非同期タスクがまだ終了していない場合は、非同期タスクが実行されて結果が取得されるまでブロックされ、待機されます。

Future の主なメソッドは次のとおりです。

  1. get() メソッド: タスクの実行結果を返します。タスクがまだ実行されていない場合は、完了するまでブロックされます。実行中に例外が発生した場合、例外がスローされますが、メインスレッドは実行されませんget() メソッドを呼び出して結果を取得しない限り、ExecutionException がスローされます。

  2. get(long timeout, TimeUnit Unit): 指定時間内のタスクの実行結果を返します。タイムアウトが返らない場合はTimeoutExceptionがスローされます。このとき、タスクは明示的にキャンセルする必要があります。

  3. cancel(boolean mayInterruptIfRunning): タスクをキャンセルします。ブール型の入力パラメータは、タスクが実行中の場合に強制的に中断するかどうかを示します。

  4. isDone(): タスクが実行されたかどうかを判断します。実行の完了は、タスクが正常に実行されることを意味しません。たとえば、タスクが失敗したが実行された場合、またはタスクが中断されたが実行された場合は、true を返します。後続のタスクが実行されない状態を示すだけです。

  5. isCancelled(): タスクがキャンセルされたかどうかを判断します。

参考ブログ投稿

参考2

呼び出し可能

Runnable は jdk1.0 から来ており、Callable は jdk1.5 から来ているので、前者用に Callable を強化する必要があります。

Runnable の run メソッドと Callable の call メソッドを比較します。Callable的call方法有返回值并可以抛出异常,而run方法没有返回值

// Runnable
public abstract void run();

// Callable
V call() throws Exception;

Callable の基本的な使い方

FutureTaskをスレッドプールに渡す

FutureTask を通じて、それを実行のためにスレッド プールに渡し、ブロックして結果が返されるのを待ちます。

  1. 送信を直接使用します:callable的逻辑是在异步线程中处理的,主线程通过阻塞式接受异步线程返回的内容。簡単に言うと、 Callable で実行されるコンテンツは別のスレッドで実行されますが、このスレッドはブロックされているため、メイン スレッドが実行を続ける前に実行する必要があるということです。

        public static void main(String[] args) throws Exception {
          
          
            FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
          
          
                public String call() throws InterruptedException {
          
          
                    Thread.sleep(3000L);
                    System.out.println("当前线程:" + Thread.currentThread().getName());
                    return "hello world";
                }
            });
            ExecutorService executorService = Executors.newFixedThreadPool(1);
            System.out.println("开始时间戳为:" + System.currentTimeMillis());
            executorService.submit(futureTask);
            String result = futureTask.get();
            System.out.println("结束时间戳为:" + System.currentTimeMillis() + ",result = " + result);
        }
    结果打印:
    开始时间戳为:...17192
    当前线程:pool-1-thread-1
    结束时间戳为:...20202,result = hello world
    
  2. FutureTask.run() を直接使用する: Runnable を直接呼び出すのと同じで、常にメインスレッドによって実行されます。

        public static void main(String[] args) throws Exception {
          
          
            FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
          
          
                public String call() throws InterruptedException {
          
          
                    Thread.sleep(3000L);
                    System.out.println("当前线程:" + Thread.currentThread().getName());
                    return "hello world";
                }
            });
            System.out.println("开始时间戳为:" + System.currentTimeMillis());
            futureTask.run();
            String result = futureTask.get();
            System.out.println("结束时间戳为:" + System.currentTimeMillis() + ",result = " + result);
        }
    结果打印:
    开始时间戳为:...59794
    当前线程:main
    结束时间戳为:...62796,result = hello world
    
スレッド プールを通じて実行され、Future オブジェクトが返されます。

このメソッドには、上記のメソッド 1 と同じ実行効果があります。つまり、戻り結果は Future であり、実際には FutureTask です。

    public static void main(String[] args) throws Exception {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        System.out.println("开始时间戳为:" + System.currentTimeMillis());
        Future<String> future = executorService.submit(new Callable<String>() {
    
    
            public String call() throws InterruptedException {
    
    
                Thread.sleep(3000L);
                System.out.println("当前线程:" + Thread.currentThread().getName());
                return "hello world";
            }
        });
        String result = future.get();
        System.out.println("结束时间戳为:" + System.currentTimeMillis() + ",result = " + result);
    }
结果打印:
开始时间戳为:...55569
当前线程:pool-1-thread-1
结束时间戳为:...58583,result = hello world

原理分析

スレッドプールのSubmitメソッド
    // FutureTask传入方式调用的submit方法
    public Future<?> submit(Runnable task) {
    
    
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    // Callable传入方式调用的submit方法
    public <T> Future<T> submit(Callable<T> task) {
    
    
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
future.get メソッドはブロックし、非同期スレッドの実行結果を待ちます。

FutureTask内部维护了任务进行的状态,当异步任务完成时,会将状态码设置为已完成,如果发生异常,会将状态码设置成对应的异常状态码。
ここに画像の説明を挿入します

    public V get() throws InterruptedException, ExecutionException {
    
    
        int s = state;
        if (s <= COMPLETING)
            // 如果状态还在进行中,或者刚创建,就阻塞等待
            s = awaitDone(false, 0L);
        // 调用Report,返回结果或者抛出异常
        return report(s);
    }

    private V report(int s) throws ExecutionException {
    
    
        Object x = outcome;
        if (s == NORMAL)
            // 状态正常,返回结果
            return (V)x;
        if (s >= CANCELLED)
            // 状态取消,抛出取消异常
            throw new CancellationException();
        // 抛出程序执行异常
        throw new ExecutionException((Throwable)x);
    }












要約する

参考ブログ投稿:

  1. ブログ投稿 1
  2. ブログ投稿 2
  3. ブログ投稿 3

おすすめ

転載: blog.csdn.net/yyuggjggg/article/details/125937087#comments_28564754