【スレッドプール】スレッドプールを作成する7つの方法を詳しく解説

1. スレッド プールとは何ですか?

スレッド プール (ThreadPool) は、プーリングのアイデアに基づいてスレッドを管理および使用するためのメカニズムです。複数のスレッドをあらかじめ「プール」に格納しておき、タスク発生時にスレッドの再作成や破棄によるパフォーマンスのオーバーヘッドを回避し、該当するスレッドを「プール」から取り出すだけでタスクを実行できます。 。

池化思想在计算机的应用也比较广泛,比如以下这些:

  • メモリ プーリング: メモリを事前に適用し、メモリ アプリケーションの速度を向上させ、メモリの断片化を軽減します。
  • 接続プーリング: データベース接続を事前に申請することで、接続の申請速度が向上し、システムのオーバーヘッドが削減されます。
  • オブジェクト プーリング: オブジェクトをリサイクルして、初期化および解放時のコストのかかるリソースの損失を削減します。

线程池的优势主要体现在以下 4 点:

  • リソース消費の削減: プーリング テクノロジを通じて作成されたスレッドを再利用し、スレッドの作成と破棄によって生じる損失を削減します。
  • 応答性の向上: タスクが到着すると、スレッドの作成を待たずにすぐに実行されます。
  • スレッドの管理性の向上: スレッドは希少なリソースであるため、無制限に作成するとシステム リソースを消費するだけでなく、スレッドの不合理な分散によるリソース スケジューリングの不均衡を引き起こし、システムの安定性が低下します。スレッド プールを使用して、統合された割り当て、チューニング、監視を行います。
  • より強力な機能を提供: スレッド プールはスケーラブルであるため、開発者はより多くの機能を追加できます。たとえば、遅延タイミング スレッド プール ScheduledThreadPoolExecutor を使用すると、タスクを延期したり定期的に実行したりできます。

同时阿里巴巴在其《Java开发手册》中也强制规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

  • 説明: スレッド プールの利点は、スレッドの作成と破棄にかかる時間とシステム リソースのオーバーヘッドを削減し、リソース不足の問題を解決することです。
  • スレッド プールを使用しない場合、システムが同じタイプのスレッドを多数作成し、メモリの消費または「オーバースイッチング」が発生する可能性があります。

スレッド プールとは何か、スレッド プールを使用する理由を理解した後、スレッド プールの使用方法を見てみましょう。

2. スレッドプールの分類

スレッド プールを作成するには 7 つの方法がありますが、一般に次の 2 つのカテゴリに分類できます。
ここに画像の説明を挿入

  1. Executors.newFixedThreadPool: 同時スレッドの数を制御できる固定サイズのスレッド プールを作成します。過剰なスレッドはキュー内で待機します。
  2. Executors.newCachedThreadPool: キャッシュ可能なスレッド プールを作成します。スレッドの数が処理要件を超える場合、キャッシュは一定期間後にリサイクルされます。スレッドの数が十分でない場合は、新しいスレッドが作成されます。
  3. Executors.newSingleThreadExecutor: 単一のスレッド数でスレッド プールを作成します。これにより、先入れ先出しの実行順序が保証されます。
  4. Executors.newScheduledThreadPool: 遅延タスクを実行できるスレッド プールを作成します。
  5. Executors.newSingleThreadScheduledExecutor: 遅延タスクを実行できるシングルスレッド スレッド プールを作成します。
  6. Executors.newWorkStealingPool: プリエンプティブ実行用のスレッド プールを作成します (タスクの実行順序は不確かです) [JDK 1.8 追加]。
  7. ThreadPoolExecutor: スレッド プールを作成する最も原始的な方法で、設定用の 7 つのパラメーターが含まれています。これについては後で詳しく説明します。

次に、各スレッド プール作成の具体的な使用方法を見てみましょう。

3. スレッドプールの使用

固定スレッドプール
固定サイズのスレッド プールを作成すると、同時スレッドの数を制御でき、超過したスレッドはキューで待機します。

public static void fixedThreadPool() {
    
    
    // 创建线程池
    ExecutorService threadPool = Executors.newFixedThreadPool(2);
    // 执行任务
    threadPool.execute(() -> {
    
    
        System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
    });
}

ここに画像の説明を挿入
キャッシュされたスレッドプール
キャッシュ可能なスレッド プールを作成します。スレッドの数が処理要件を超える場合、キャッシュは一定期間後にリサイクルされます。スレッドの数が十分でない場合は、新しいスレッドが作成されます。

public static void cachedThreadPool() {
    
    
    // 创建线程池
    ExecutorService threadPool = Executors.newCachedThreadPool();
    // 执行任务
    for (int i = 0; i < 10; i++) {
    
    
        threadPool.execute(() -> {
    
    
            System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
        });
    }
}

ここに画像の説明を挿入
シングルスレッドエグゼキュータ
単一のスレッド数でスレッド プールを作成します。これにより、先入れ先出しの実行順序が保証されます。

public static void singleThreadExecutor() {
    
    
    // 创建线程池
    ExecutorService threadPool = Executors.newSingleThreadExecutor();
    // 执行任务
    for (int i = 0; i < 10; i++) {
    
    
        final int index = i;
        threadPool.execute(() -> {
    
    
            System.out.println(index + ":任务被执行");
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
            }
        });
    }
}

ここに画像の説明を挿入
スケジュールされたスレッドプール
遅延タスクを実行できるスレッド プールを作成します。

public static void scheduledThreadPool() {
    
    
    // 创建线程池
    ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
    // 添加定时执行任务(1s 后执行)
    System.out.println("添加任务,时间:" + new Date());
    threadPool.schedule(() -> {
    
    
        System.out.println("任务被执行,时间:" + new Date());
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
        }
    }, 1, TimeUnit.SECONDS);
}

ここに画像の説明を挿入
SingleThreadScheduledExecutor
遅延タスクを実行できるシングルスレッドのスレッド プールを作成します。

public static void SingleThreadScheduledExecutor() {
    
    
    // 创建线程池
    ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
    // 添加定时执行任务(2s 后执行)
    System.out.println("添加任务,时间:" + new Date());
    threadPool.schedule(() -> {
    
    
        System.out.println("任务被执行,时间:" + new Date());
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
        }
    }, 2, TimeUnit.SECONDS);
}

ここに画像の説明を挿入
新しい仕事盗むプール
プリエンプティブ実行用のスレッド プールを作成します (タスクの実行順序は不定です) この方法は、JDK 1.8 以降のバージョンでのみ使用できることに注意してください。

public static void workStealingPool() {
    
    
    // 创建线程池
    ExecutorService threadPool = Executors.newWorkStealingPool();
    // 执行任务
    for (int i = 0; i < 10; i++) {
    
    
        final int index = i;
        threadPool.execute(() -> {
    
    
            System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
        });
    }
    // 确保任务执行完成
    while (!threadPool.isTerminated()) {
    
    
    }
}

ここに画像の説明を挿入
スレッドプールエグゼキュータ
スレッド プールを作成する最も原始的な方法で、設定用の 7 つのパラメーターが含まれています。

public static void myThreadPoolExecutor() {
    
    
    // 创建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
    // 执行任务
    for (int i = 0; i < 10; i++) {
    
    
        final int index = i;
        threadPool.execute(() -> {
    
    
            System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
    }
}

ここに画像の説明を挿入

4. ThreadPoolExecutorの詳細説明

上記の検討の後、スレッド プール全体についてもある程度理解できました。では、スレッド プールを選択するにはどうすればよいでしょうか?

Alibaba の「Java Development Manual」から得られた答えを見てみましょう。

  • [必須要件] スレッド プールは Executor を使用して作成することはできませんが、ThreadPoolExecutor を介して作成する必要があります。この処理方法により、書き込みを行う学生はスレッド プールの実行ルールをより明確にし、リソース枯渇のリスクを回避できます。
  • 注: エグゼキュータによって返されるスレッド プール オブジェクトの欠点は次のとおりです。
    1) FixedThreadPool および SingleThreadPool: 許可されるリクエスト キューの長さは Integer.MAX_VALUE であり、大量のリクエストが蓄積され、OOM が発生する可能性があります。
    2) CachedThreadPool: 作成できるスレッドの数は Integer.MAX_VALUE です。これにより、多数のスレッドが作成され、OOM が発生する可能性があります。

要約すると、ThreadPoolExecutor メソッドを使用してスレッド プールを作成することをお勧めします。この作成方法はより制御しやすく、スレッド プールの実行ルールが明確になり、未知のリスクを回避できるからです。

ThreadPoolExecutor パラメータの概要

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

参数 1:corePoolSize
コアスレッドの数、つまりスレッドプール内で常に稼働しているスレッドの数。

参数 2:maximumPoolSize
スレッドの最大数、スレッド プールで許可されるスレッドの最大数、およびスレッド プールのタスク キューがいっぱいの場合に作成できるスレッドの最大数。

参数 3:keepAliveTime
存続できるスレッドの最大数は、スレッド内でタスクの実行がない場合、最大数のスレッドによってスレッドの一部が破棄され、最終的にコア スレッドの数が維持されます。

参数 4:unit:
この単位は、スレッドの生存時間を設定するために一緒に使用されるパラメーター 3 の生存時間と組み合わせて使用​​されます。パラメーター keepAliveTime の時間単位には、次の 7 つのオプションがあります。

TimeUnit.DAYS:天
TimeUnit.HOURS:小时
TimeUnit.MINUTES:分
TimeUnit.SECONDS:秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MICROSECONDS:微妙
TimeUnit.NANOSECONDS:纳秒

参数 5:workQueue
スレッド プールによる実行を待機しているタスクを格納するために使用されるブロッキング キューは、スレッド セーフです。これには次の 7 つのタイプが含まれます: より一般的に使用されるものは LinkedBlockingQueue と同期、およびスレッド プールのキュー戦略ですBlockingQueue に関連しています。

ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

参数 6:threadFactory
スレッド ファクトリは主にスレッドの作成に使用され、デフォルトでは通常の優先順位の非デーモン スレッドになります。

参数 7:handler
拒否ポリシー、タスクを拒否するときのポリシー、システムは 4 つのオプションを提供します。デフォルトのポリシーは AbortPolicy です。

AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。

ThreadPoolExecutor実行処理
ThreadPoolExecutor の主要ノードの実行フローは次のとおりです。

  • スレッド数がコアスレッド数より少ない場合にスレッドを作成します。
  • スレッド数がコアスレッド数以上で、タスクキューが満杯でない場合、タスクをタスクキューに入れます。
  • スレッド数がコア スレッド数以上でタスク キューがいっぱいの場合: スレッド数が最大スレッド数未満の場合はスレッドを作成し、スレッド数がコア スレッド数と等しい場合はスレッドを作成します。スレッドの最大数を指定すると、例外がスローされ、タスクが拒否されます。
    ここに画像の説明を挿入

ThreadPoolExecutor のカスタム拒否戦略

public static void main(String[] args) {
    
    
    // 任务的具体方法
    Runnable runnable = new Runnable() {
    
    
        @Override
        public void run() {
    
    
            System.out.println("当前任务被执行,执行时间:" + new Date() +
                               " 执行线程:" + Thread.currentThread().getName());
            try {
    
    
                // 等待 1s
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    };
    // 创建线程,线程的任务队列的长度为 1
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
                                                           100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
                                                           new RejectedExecutionHandler() {
    
    
                                                               @Override
                                                               public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    
    
                                                                   // 执行自定义拒绝策略的相关操作
                                                                   System.out.println("我是自定义拒绝策略~");
                                                               }
                                                           });
    // 添加并执行 4 个任务
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
}

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/m0_46638350/article/details/130925464