スレッドプールの役割とJavaでのスレッドプールの使用方法を説明する

サーバー側のアプリケーション(データベースやWebサーバーなど)は、クライアントからの同時実行性が高く、消費量の少ない要求を処理する必要があるため、これらの要求を処理するために必要なスレッドを頻繁に作成することは、非常にリソースを消費する操作です。従来の方法は、新しいリクエストに対して新しいスレッドを作成する方法です。この方法は実装が簡単に見えますが、大きな欠点があります。リクエストごとに新しいスレッドを作成すると、スレッドを作成および破棄するときに、より多くの時間とシステムリソースが必要になります。したがって、同時に作成するスレッドが多すぎると、システムメモリが不足する可能性があります。これには、作成するスレッドの数を制限する必要があります。つまり、スレッドプールを使用する必要があります。

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

スレッドプールテクノロジは、以前に作成されたスレッドを使用して現在のタスクを実行するスレッド再利用テクノロジであり、スレッドサイクルのオーバーヘッドとリソースの競合の問題に対するソリューションを提供します。リクエストの到着時にスレッドがすでに存在するため、スレッド作成プロセスに起因する遅延が排除され、アプリケーションはより高速に応答できます。

  • Javaは、ExecutorインターフェースとそのサブインターフェースExecutorServiceおよびThreadPoolExecutorを中心としたexecutorフレームワークを提供しますExecutorを使用することにより、Runnableインターフェースを実装するだけで、スレッドタスクを完了し、実行のためにexecutorに渡すことができます。
  • スレッドプールをカプセル化し、プログラミングタスクを、スレッドの実装メカニズムではなく、特定のタスクの実現に集中させます。
  • スレッドプールを使用するには、最初にExecutorServiceオブジェクトを作成してから、一連のタスクをそのオブジェクトに渡します。ThreadPoolExcutorクラスは、スレッドプールの初期化と最大スレッド容量を設定できます。

上の図は、スレッドプールの初期化に3つのスレッドがあり、タスクキューで実行されるタスクオブジェクトが5つあることを示しています。

エグゼキュータスレッドプールメソッド|メソッド|説明|| --- | --- | | newFixedThreadPool(int)|固定数のスレッドでスレッドプールを作成します。intパラメータはスレッドプール内のスレッド数を示します|| newCachedThreadPool ()|アイドル状態のスレッドを柔軟に再利用できるキャッシュ可能なスレッドプールを作成します。アイドル状態のスレッドがない場合は、タスクを処理するための新しいスレッドを作成します。|| newSingleThreadExecutor()|シングルスレッドスレッドプールを作成します。タスクの実行には単一のワーカースレッドのみを使用します|| newScheduledThreadPool |固定長スレッドプールを作成し、タイミングと定期的なタスク実行をサポートします|

固定スレッドプールの場合、エグゼキュータが現在すべてのスレッドを実行していると、保留中のタスクがキューに入れられ、スレッドがアイドル状態になったときに実行されます。

2、スレッドプールの例

以下のコンテンツでは、スレッドプールのエグゼキュータを紹介します。

タスクを処理するためのスレッドプールを作成するために従う手順

  1. 特定のタスクロジックを実行するためのタスクオブジェクトの作成(Runnableインターフェイスの実装)
  2. Executorsを使用してスレッドプールExecutorServiceを作成します
  3. 実行するタスクオブジェクトをExecutorServiceに渡してタスクを処理します
  4. エグゼキュータスレッドプールを停止します
//第一步: 创建一个任务对象(实现Runnable接口),用于执行具体的任务逻辑 (Step 1) 
class Task implements Runnable  {
    private String name;

    public Task(String s) {
        name = s;
    }

    // 打印任务名称并Sleep 1秒
    // 整个处理流程执行5次
    public void run() {
        try{
            for (int i = 0; i<=5; i++) {
                if (i==0) {
                    Date d = new Date();
                    SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
                    System.out.println("任务初始化" + name +" = " + ft.format(d));
                    //第一次执行的时候,打印每一个任务的名称及初始化的时间
                }
                else{
                    Date d = new Date();
                    SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
                    System.out.println("任务正在执行" + name +" = " + ft.format(d));
                    // 打印每一个任务处理的执行时间
                }
                Thread.sleep(1000);
            }
            System.out.println("任务执行完成" + name);
        }  catch(InterruptedException e)  {
            e.printStackTrace();
        }
    }
}

テストケース

public class ThreadPoolTest {
    // 线程池里面最大线程数量
    static final int MAX_SIZE = 3;

    public static void main (String[] args) {
        // 创建5个任务
        Runnable r1 = new Task("task 1");
        Runnable r2 = new Task("task 2");
        Runnable r3 = new Task("task 3");
        Runnable r4 = new Task("task 4");
        Runnable r5 = new Task("task 5");

        // 第二步:创建一个固定线程数量的线程池,线程数为MAX_SIZE
        ExecutorService pool = Executors.newFixedThreadPool(MAX_SIZE);

        // 第三步:将待执行的任务对象交给ExecutorService进行任务处理
        pool.execute(r1);
        pool.execute(r2);
        pool.execute(r3);
        pool.execute(r4);
        pool.execute(r5);

        // 第四步:关闭线程池
        pool.shutdown();
    }
} 

実行結果の例

任务初始化task 1 = 05:25:55
任务初始化task 2 = 05:25:55
任务初始化task 3 = 05:25:55
任务正在执行task 3 = 05:25:56
任务正在执行task 1 = 05:25:56
任务正在执行task 2 = 05:25:56
任务正在执行task 1 = 05:25:57
任务正在执行task 3 = 05:25:57
任务正在执行task 2 = 05:25:57
任务正在执行task 3 = 05:25:58
任务正在执行task 1 = 05:25:58
任务正在执行task 2 = 05:25:58
任务正在执行task 2 = 05:25:59
任务正在执行task 3 = 05:25:59
任务正在执行task 1 = 05:25:59
任务正在执行task 1 = 05:26:00
任务正在执行task 2 = 05:26:00
任务正在执行task 3 = 05:26:00
任务执行完成task 3
任务执行完成task 2
任务执行完成task 1
任务初始化task 5 = 05:26:01
任务初始化task 4 = 05:26:01
任务正在执行task 4 = 05:26:02
任务正在执行task 5 = 05:26:02
任务正在执行task 4 = 05:26:03
任务正在执行task 5 = 05:26:03
任务正在执行task 5 = 05:26:04
任务正在执行task 4 = 05:26:04
任务正在执行task 4 = 05:26:05
任务正在执行task 5 = 05:26:05
任务正在执行task 4 = 05:26:06
任务正在执行task 5 = 05:26:06
任务执行完成task 4
任务执行完成task 5

プログラム実行結果に示すように、タスク4またはタスク5は、プール内のスレッドがアイドル状態になった場合にのみ実行されます。その前に、追加のタスクが実行されるキューに配置されます。

スレッドプールは最初の3つのタスクを実行し、スレッドプール内のスレッドは、タスク4と5を処理する前に再利用され、空にされます。

このスレッドプール方式を使用する主な利点の1つは、一度に10,000の要求を処理したいが、10,000のスレッドを作成したくない場合に、システムリソースの過度の使用によるダウンタイムを回避できることです。このメソッドを使用して、500スレッドを含むスレッドプールを作成し、500リクエストをスレッドプールに送信できます。ThreadPoolは、この時点で最大500のスレッドを作成し、一度に500のリクエストを処理します。スレッドのプロセスが完了すると、ThreadPoolは内部で501番目の要求をそのスレッドに割り当て、残りのすべての要求に対して同じ操作を実行し続けます。システムリソースが比較的タイトな場合、スレッドプールはプログラムの安定した動作を保証するための効果的なソリューションです。

3つ目は、スレッドプールの考慮事項とチューニングの使用です。

  1. デッドロック:デッドロックはどのマルチスレッドプログラムでも発生する可能性がありますが、スレッドプールでは別のデッドロックケースが発生しますこの場合、すべての実行スレッドがキュー内のブロックされたスレッドの実行結果を待機し、スレッドが実行を続行できなくなります。
  2. スレッドリーク:タスクの完了時にスレッドプール内のスレッドが正しく戻らない場合、スレッドリークが発生します。たとえば、スレッドが例外をスローし、プールクラスがこの例外をキャッチしない場合、スレッドは異常終了し、スレッドプールのサイズが1つ減少します。この状況が複数回繰り返されると、スレッドプールは最終的に空になり、他のタスクを実行するためにスレッドを使用できなくなります。
  3. 頻繁なスレッドローテーション:スレッドプールのサイズが非常に大きい場合、スレッド間のコンテキスト切り替えは多くの時間を浪費します。したがって、システムリソースが許す場合、スレッドプールが大きいほど良いというわけではありません。

スレッドプールサイズの最適化スレッドプールの最適なサイズは、使用可能なプロセッサの数と処理されるタスクの性質によって異なります。CPUを集中的に使用するタスクの場合、システムにN個の論理処理コアがあると仮定すると、最大スレッドプールサイズがNまたはN + 1の場合、最大の効率が得られます。I / Oを多用するタスクの場合、サービス処理時間(S)に対する要求の待機時間(W)の比率を考慮する必要があります。スレッドプールの最大サイズはN *(1+ W / S)です。最高の効率を達成するために。

上記の要約を独断的に使用しないでください。アプリケーションタスクの処理タイプに応じて柔軟に設定および調整する必要があり、テスト実験が不可欠です。

私のブログをフォローすることを歓迎します、多くのブティックコレクションがあります

  • この記事は、出典を示して複製されています(接続を添付する必要があり、テキストのみを複製することはできません):レターブラザーのブログ

あなたがそれがあなたに役立つと思うなら、私のためにそれを好きにして共有してください!あなたのサポートは私の尽きることのない創造的な動機です!また、最近、以下のような高品質なコンテンツを出力しておりますので、よろしくお願いいたします。

おすすめ

転載: blog.csdn.net/hanxiaotongtong/article/details/112598976
おすすめ