Java のスレッド プールの詳細な調査

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

スレッド プールは、タスクがキューに追加され、スレッドの作成後に自動的に開始されるマルチスレッド化の形式です。スレッド プールのスレッドはすべてバックグラウンド スレッドです。

スレッドプールの利点は何ですか?

次の利点があります。

  1. リソースの消費を減らします作成されたスレッドを再利用することで、スレッドの作成と破棄のコストを削減します。
  2. 応答性を向上させますタスクが到着すると、スレッドの作成を待たずにすぐにタスクを実行できます。
  3. スレッドの管理性を向上させますスレッドは希少な資源であり、無制限に作成すると、システム資源を消費するだけでなく、システムの安定性を低下させます.スレッドプールを使用すると、統一された割り当て、チューニング、および監視を実行できます.

ここに画像の説明を挿入

スレッドプールの使用

Executor は、指定されたタイプのスレッド プールを作成します

内部実装は、固定パラメータを持つ ThreadPoolExecutor に基づいています。

newSingleThreadExecutor

ワーカー スレッドが 1 つだけのスレッド プールを作成します。1 つのワーカー スレッドのみを使用してタスクを実行し、すべてのタスクが指定された順序 (FIFO、LIFO、優先度) で実行されるようにします。このスレッドが異常終了した場合、別のスレッドが代わりに実行され、順次実行が保証されます。単一のワーカー スレッドの最大の特徴は、タスクが順次実行されることが保証されており、特定の時点で複数のスレッドがアクティブになることがないことです。

ExecutorService es1 = Executors.newSingleThreadExecutor();

newFixedThreadPool

指定された数のワーカー スレッドでスレッド プールを作成します。タスクが送信されるたびに、ワーカー スレッドが作成されます. ワーカー スレッドの数がスレッド プールの初期最大数に達すると、送信されたタスクはプール キュー (実行待ちのタスクを格納するために使用されるブロッキング キュー) に格納されます.

FixedThreadPool は典型的で優れたスレッド プールであり、プログラムの効率を向上させ、スレッド作成のオーバーヘッドを節約するという利点があります。ただし、スレッド プールがアイドル状態の場合、つまりスレッド プールに実行可能なタスクがない場合、ワーカー スレッドは解放されず、特定のシステム リソースも占有されます。

ExecutorService es2 = Executors.newFixedThreadPool(3);

newCachedThreadPool

キャッシュ可能な (可変数のワーカー スレッド) スレッド プールを作成します. スレッド プールの長さが処理のニーズを超える場合, アイドル状態のスレッドを柔軟にリサイクルできます. リサイクルがない場合, 新しいスレッドが作成されます.

このタイプのスレッド プールの特徴は次のとおりです。

作成されるワーカー スレッドの数にほとんど制限がないため (実際には制限があり、その数はInteger. MAX_VALUEです)、柔軟にスレッド プールにスレッドを追加できます。

タスクがスレッド プールに長時間送信されない場合、つまりワーカー スレッドが指定された ( keepAliveTime ) 時間 (デフォルトは 1 分) アイドル状態である場合、ワーカー スレッドは自動的に終了します。終了後に新しいタスクを送信すると、スレッド プールはワーカー スレッドを再作成します。

CachedThreadPool を使用する場合は、タスク数の制御に注意する必要があります。そうしないと、多数のスレッドが同時に実行されるため、システム OOM が発生する可能性があります。

ExecutorService es3 = Executors.newCachedThreadPool();

newScheduledThreadPool

ワーカー スレッドの数を指定して定期的にタスクを実行し、タイミングと定期的なタスクの実行をサポートし、タイミングと定期的なタスクの実行をサポートするスレッド プールを作成します。

ScheduledExecutorService es4 = Executors.newScheduledThreadPool(2);

newSingleThreadScheduledExecutor

単一の数のワーカー スレッドを作成し、定期的にタスクを実行するスレッド プール。特定の遅延の後または定期的にコマンドを実行するようにスケジュールできます。スレッド プールでは最大 1 つのスレッドを実行できます。後で送信されたスレッド アクティビティは実行のためにキューに入れられ、スレッド アクティビティは定期的に実行することも、遅延させることもできます。

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

ThreadPoolExecutor はカスタム スレッド プールを作成します

カスタム スレッド プールを作成すると、パラメーターをカスタマイズできます。

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                   long keepAliveTime,
                   TimeUnit unit,
                   BlockingQueue<Runnable> workQueue);

ご覧のとおり、次のパラメーターが必要です。

corePoolSize : スレッド プールの基本サイズ、つまり、タスクを実行する必要がないときのスレッド プールのサイズであり、ワーク キューがいっぱいの場合にのみ、この数を超えるスレッドが作成されます。スレッド プールが作成されると、スレッド プール内のスレッドの数は 0 になります。タスクが来ると、タスクを実行するためにスレッドが作成されます。スレッド、つまりタスクがない場合 到着前に corePoolSize スレッドまたは 1 つのスレッドを作成します。スレッド プール内のスレッド数が corePoolSize に達すると、到着したタスクがキャッシュ キュー (workQueue) に配置されます。

maximumPoolSize : スレッド プールで許可されるスレッドの最大数。スレッド プール内の現在のスレッド数がこの値を超えることはありません。キュー内のタスクがいっぱいで、現在のスレッド数が maximumPoolSize 未満の場合、タスクを実行するために新しいスレッドが作成されます。ここで言及する価値があるのは、ライフ サイクル全体でスレッド プールに表示されたスレッドの最大数を記録する maximumPoolSize です。なんで一回言うの?スレッド プールが作成された後、setMaximumPoolSize() を呼び出してスレッドの最大数を変更できるためです。

keepAliveTime : 実行するタスクがない場合にスレッドが終了する時間を示します。デフォルトでは、keepAliveTime は、スレッド プール内のスレッド数が corePoolSize よりも大きくなるまで、スレッド プール内のスレッド数が corePoolSize よりも大きい場合にのみ機能します。スレッド プール内のスレッドの数が corePoolSize よりも大きく、スレッドが keepAliveTime の間アイドル状態である場合、スレッドは破棄されます。ただし、allowCoreThreadTimeOut(boolean) メソッドが呼び出された場合、スレッド プール内のスレッド数が corePoolSize より大きくない場合、keepAliveTime パラメーターはスレッド プール内のスレッド数が 0 になるまで機能します。

unit : パラメータ keepAliveTime の時間単位。java.util.concurrent.TimeUnit クラスには 7 つの値があります。

  1. imeUnit.DAYS 日
  2. TimeUnit.HOURS 時間
  3. TimeUnit.MINUTES 分
  4. TimeUnit.SECONDS        秒
  5. TimeUnit.MILLISECONDS ミリ秒
  6. TimeUnit.MICROSECONDS  微妙
  7. TimeUnit.NANOSECONDS ナノ秒

Task queue-workQueue : 実行待ちのタスクを格納するために使用されるブロッキング キュー (Runnable)。このパラメータの選択も非常に重要です。スレッド プールの実行中のプロセス、ブロッキング キューに大きな影響を与えます。次のオプションがあり、すべて BlockingQueue の実装です。

  1. ArrayBlockingQueue: 配列構造で構成された制限付きブロッキング キュー (配列構造はポインターと連携してリング キューを実装できます)。
  2. LinkedBlockingQueue: リンクされたリスト構造で構成される制限付きブロッキング キュー容量が指定されていない場合、容量はデフォルトで Integer.MAX_VALUE になります。
  3. SynchronousQueue: 要素を格納しないブロッキング キュー. コンシューマー スレッドが take() メソッドを呼び出すと、プロデューサー スレッドが要素を生成し、コンシューマー スレッドが要素を取得して戻るまでブロックされます; プロデューサー スレッドもput() メソッドを呼び出すときにブロックし、プロデューサーはコンシューマー スレッドが要素を消費するまで戻りません。
  4. PriorityBlockingQueue: 優先度の並べ替えをサポートする無制限のブロッキング キュー. 要素の要件はありません. Comparable インターフェイスを実装するか、Comparator を提供してキュー内の要素を比較できます. 時間とは関係なく、優先順位に従ってタスクを実行するだけです。
  5. DelayQueue: PriorityBlockingQueue と同様に、バイナリ ヒープによって実装される無制限の優先度ブロッキング キューです。すべての要素は Delayed インターフェイスを実装する必要があり、タスクは実行遅延によってキューから抽出され、時間切れになる前にタスクを取得することはできません。
  6. LinkedBlockingDeque: 双方向キューを使用して実装された境界付き両端ブロッキング キュー。ダブルエンドとは、通常のキューのように FIFO (先入れ先出し) することも、スタックのように FILO (先入れ後出し) にすることもできることを意味します。
  7. LinkedTransferQueue: ConcurrentLinkedQueue、LinkedBlockingQueue、SynchronousQueue の組み合わせですが、LinkedBlockingQueue と同じ動作をする ThreadPoolExecutor で使用されますが、無制限のブロッキング キューです。

ArrayBlockingQueueそして、PriorityBlockingQueueless を使用し、通常はLinkedBlockingQueueandを使用しますSynchronousQueueスレッドプールのキューイング戦略がBlockingQueue関係しています。

バウンド キューとアンバウンド キューの違いに注意してください: バウンド キューを使用する場合、キューが飽和してスレッドの最大数を超えたときに拒否戦略が実行されます。アンバウンド キューを使用する場合は、タスク キューがいつでもタスクを追加できるので、 maximumPoolSize を設定しても意味がありません。

Thread factory-threadFactory : スレッドを作成するためのファクトリを設定するために使用されますThreadFactory. 作成されたスレッドごとに、デーモンや優先度の設定など、スレッド
ファクトリを介してより意味のあることを行うことができます. Executors フレームワークは、スレッドのデフォルトを実装しています工場:

/**
 * The default thread factory.
 */
private static class DefaultThreadFactory implements ThreadFactory {
    
    
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
 
    DefaultThreadFactory() {
    
    
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }
 
    public Thread newThread(Runnable r) {
    
    
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

Reject strategy-handler : 処理タスクを拒否するときの戦略をRejectedExecutionHandler次の 4 つの値で示します。AbortPolicy

RejectedExecutionHandler defaultHandler = new AbortPolicy();
  • AbortPolicy: 例外を直接スローします (デフォルト)。
  • CallerRunsPolicy: 呼び出し元のスレッドのみを使用してタスクを実行します。
  • DiscardOldestPolicy: キュー内の最新のタスクを破棄し、現在のタスクを実行します。
  • DiscardPolicy: 処理しないで破棄します。
  • カスタム戦略: RejectedExecutionHandler インターフェースを実装します。
  • workers : スレッド プール内のワーカー スレッドはすべてこのコレクション HashSet に配置されます. Worker はスレッド プール内のスレッドです. Task は実行可能ですが、実際には実行されませんが、run メソッドは Worker によって呼び出されます.

    Worker クラス自体は Runnable を実装するだけでなく、AbstractQueuedSynchronizer (以下、AQS という) を継承しているため、実行可能なタスクであるだけでなく、ロックの効果も実現できます. Worker によって実装される AQS は再入不可のロックです. ワーカーロックを取得するためにそれ以外の場合は、ロックが必要な他のメソッドをいくつか入力します。

    HashSet<Worker> workers = new HashSet<Worker>();
    

    mainLock: スレッドがタスクを実行するときに使用されるロック。

    ReentrantLock mainLock = new ReentrantLock();
    

    termination:ロック条件

    Condition termination = mainLock.newCondition();
    

    スレッドプールの仕組み

    スレッドプールがどのように機能するかを説明し、上記のパラメーターをより深く理解しましょう。その動作原理のフローチャートは次のとおりです。
    ここに画像の説明を挿入

    機能スレッドプール

    上のスレッドプールの使い方が面倒くさい?実際、Executor は、次のように、4 つの一般的な機能スレッド プールを既にカプセル化しています。

    定长线程池(FixedThreadPool)
    定时线程池(ScheduledThreadPool )
    可缓存线程池(CachedThreadPool)
    单线程化线程池(SingleThreadExecutor)
    

    固定長スレッド プール (FixedThreadPool)

    作成方法のソースコード:

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

    特徴: コアスレッドのみあり, スレッド数固定, 実行後すぐに再利用される. タスクキューは連結リスト構造のバウンデッドキューである.
    アプリケーション シナリオ: 同時スレッドの最大数を制御します。

    		// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
          
          
    		  public void run() {
          
          
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		fixedThreadPool.execute(task);
    

    タイミング スレッド プール (ScheduledThreadPool)

    作成方法のソースコード:

    		private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
    		 
    		public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
          
          
    		    return new ScheduledThreadPoolExecutor(corePoolSize);
    		}
    		public ScheduledThreadPoolExecutor(int corePoolSize) {
          
          
    		    super(corePoolSize, Integer.MAX_VALUE,
    		          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
    		          new DelayedWorkQueue());
    		}
    		 
    		public static ScheduledExecutorService newScheduledThreadPool(
    		        int corePoolSize, ThreadFactory threadFactory) {
          
          
    		    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    		}
    		public ScheduledThreadPoolExecutor(int corePoolSize,
    		                                   ThreadFactory threadFactory) {
          
          
    		    super(corePoolSize, Integer.MAX_VALUE,
    		          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
    		          new DelayedWorkQueue(), threadFactory);
    		}
    

    特徴: コアスレッドの数は固定で、非コアスレッドの数は無制限です. 実行後、10msのアイドル時間後にリサイクルされます. タスクキューは遅延ブロッキングキューです.
    アプリケーション シナリオ: タイミングまたは定期的なタスクを実行します。

    例:

    		// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
    		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
          
          
    		  public void run() {
          
          
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
    		scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
    

    キャッシュ可能なスレッド プール (CachedThreadPool)

    作成方法のソースコード:

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

    機能: コア スレッドなし、非コア スレッドの数に制限なし、60 秒のアイドル時間後にリサイクル、タスク キューは要素を格納しないブロッキング キューです。
    アプリケーション シナリオ: より少ない時間で多数のタスクを実行します。

    		// 1. 创建可缓存线程池对象
    		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
          
          
    		  public void run() {
          
          
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		cachedThreadPool.execute(task);
    

    シングルスレッド スレッド プール (SingleThreadExecutor)

    作成方法のソースコード:

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

    特徴: コア スレッドは 1 つだけで、非コア スレッドはなく、実行後すぐに再利用され、タスク キューは連結リスト構造の境界キューです。
    アプリケーション シナリオ: 同時実行には適していないが、データベース操作、ファイル操作など、IO ブロッキングを引き起こし、UI スレッドの応答に影響を与える可能性がある操作。

    		// 1. 创建单线程化线程池
    		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
          
          
    		  public void run() {
          
          
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		singleThreadExecutor.execute(task);
    

    ここに画像の説明を挿入
    参考:Androidマルチスレッディング: JavaマルチスレッディングのThreadPool総合分析:スレッドプールjavaの
    スレッドプールを徹底解説

おすすめ

転載: blog.csdn.net/weixin_61341342/article/details/130057229