[マルチスレッド]-詳細なスレッドプール

1.スレッドプールの概要

1.スレッドプールとは

まず、Baidu百科事典の回答を参照してください。
スレッドプールはマルチスレッド処理の形式です。タスクは処理中にキューに追加され、スレッドの作成後にこれらのタスクが自動的に開始されます。
次に、Baidu Baikeの回答を使用して、さらに定義することができます。
プロセスの開始時に特定の数のスレッドを作成し、それらをプールに追加して作業を待機します。サーバーはリクエストを受信すると、プール内のスレッドをウェイクアップし(使用可能なスレッドがある場合)、サービスが必要なリクエストをサーバーに渡します。スレッドがサービスを完了すると、プールに戻り、作業を待ちます。プールに使用可能なスレッドがない場合、サーバーは空きスレッドができるまで待機します。

2.スレッドプールを使用する理由

ここでは、最初にマルチスレッドを使用して、単純な累積シナリオを実装します。

public class Demo01 {
    
    

	public static int count = 0;
	public static int Max = 100000;

	public static void main(String[] args) throws InterruptedException {
    
    
		long currentTimeMillis = System.currentTimeMillis();
		while (count < Max) {
    
    
			Thread thread = new Thread(() -> {
    
    
				count++;
			});
			thread.start();
			thread.join();
		}
		System.out.println("多线程执行时长:" + (System.currentTimeMillis() - currentTimeMillis));
	}
	}

実行結果を見てみましょ
ここに画像の説明を挿入
ここでは、マルチスレッド処理を使用しているため、単純なカウント累積操作がシングルスレッド処理よりもさらに長く、スレッドの作成とインスタンス化が間違いなく行われていることがわかりますCPUコンテキストの切り替えと追加のメモリオーバーヘッドにより、明らかに気が進まなくなります。
次に、スレッドプールの使用方法を見ていきます。

public class Demo01 {
    
    

	public static int count = 0;
	public static int Max = 100000;

	public static void main(String[] args) throws InterruptedException {
    
    
		ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
		long currentTimeMillis = System.currentTimeMillis();
		while (count < Max) {
    
    
			newCachedThreadPool.execute(() -> {
    
    
				count++;
			});
		}
		System.out.println("线程池执行时长:" + (System.currentTimeMillis() - currentTimeMillis));
		newCachedThreadPool.shutdown();
	}
	}

結果:
ここに画像の説明を挿入
では、なぜスレッドプールはそれほど多くの時間を節約できるのでしょうか?

3.スレッドプールの実現原理

一種のプーリング技術として、スレッドプールは実際には一種のコンテナです。コンテナの実装に使用されるデータ構造は実際には2つあります。

  1. 最初のデータ構造は、ワーカースレッドのコレクションを格納するために使用されます
  2. 2番目のデータ構造は、実行されるタスクのキューを格納するために使用されます

ここでは、単純に絵を描き
ここに画像の説明を挿入
ます。ユーザーがタスクをスレッドプールに送信すると、最初にタスクがブロックキューに入れられ、次にスレッドセット内のスレッドが引き続きキューに移動してタスクを取得して実行します。 。タスクがすべて実行されると、スレッドコレクション内のスレッドがブロックされます。これは、待機方法について説明するときに前述した生産/消費モデルのように見えますか?では、どのようにスレッドプールを作成するのでしょうか?

4.スレッドプールを作成する方法

通常、スレッドプールを作成するには2つの方法を使用します。

4.1工法により作成

ここでは、最初に、構築メソッドを使用してスレッドプールを作成する方法を確認します。

public class Demo01 {
    
    

	public static void main(String[] args) {
    
    
		new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
	}
}

実際、コンストラクターを使用してスレッドプールを作成することは難しくありません。重要なことは、関連するパラメーターのいくつかです。
ここに画像の説明を挿入

  1. corePoolSize:スレッドプール内のコアスレッドの数を示します。コアスレッドは、スレッドプールがアイドル状態のときにリサイクルされないスレッドの一部を指します。コアスレッドを設定すると、スレッドの作成とリサイクルに起因するパフォーマンスのオーバーヘッドを効果的に削減できます。もちろん、コアスレッドを大きすぎたり小さすぎたりするのはあまり良くないことに注意してください。簡単な方法でコアスレッドを計算できます。式最も費用効果の高い設定はどれくらいですか:コアスレッドの数= CPUコアの数* CPU使用率*(1 +スループット)。もちろん、単純なアプリケーションの場合は、2 * CPUコアの数を直接設定し、int availableProcessors = Runtime.getRuntime()。availableProcessors();によってCPUコアの数を取得できます。
  2. maximumPoolSize:スレッドプール内のスレッドの最大数。スレッドプールに含めることができる最大容量を指します。最大スレッド数の設定は、ビジネスシナリオを参照するのに最適です。大きすぎる設定はお勧めしません。 、OOMの問題(メモリ不足)を引き起こす可能性があるため。例外)。
  3. keepAliveTime:キープアライブ時間。これは、現在のスレッド数がコアスレッド数よりも多い場合に、アイドル状態の非コアスレッドの最大存続時間を指します。アイドル時間が最大生存時間よりも長い場合、アイドルスレッドのライフサイクルは終了します。
  4. TimeUnit:時間、分、秒などの待機時間の単位は何ですか。
  5. workQueue:スレッドプールの実装に使用される待機キューの種類を示します。一般的なキュー構造は、LinkedBlockedQueue、SynchronousQueueなどです。

4.2ファクトリメソッド

もちろん、構築メソッドが面倒だと感じた場合、JavaはファクトリメソッドクラスExecutorsも提供します
ここに画像の説明を挿入
これらのさまざまなファクトリメソッドによって作成されるさまざまなスレッドプールについて説明する前に、ファクトリメソッドを使用してスレッドプールを作成する方法は、実稼働環境での使用には推奨されないことに言及する必要があります。理由も非常に単純です。ファクトリメソッドによって作成されたスレッドプール最大スレッド数について説明したときに前述したように、デフォルトの最大スレッド数はInteger.MAX_VALUEです。最大スレッド数が多すぎると、メモリオーバーフローが発生します。
次に、いくつかの一般的なスレッドプールについて簡単に説明します。

  1. newCachedThreadPool:キャッシュ可能なスレッドプールを作成します。スレッドプールの長さが処理のニーズを超える場合、アイドル状態のスレッドを柔軟にリサイクルできます。リサイクル可能なものがない場合は、新しいスレッドが作成されます。
    2. newFixedThreadPool:固定長のスレッドプールを作成し、タスクが送信されるたびにワーカースレッドを作成します。ワーカースレッドの数がスレッドプールで設定された最大数に達すると、スレッドプール内のスレッドに送信されます。タスクを完了します。
    3. newSingleThreadExecutor:シングルスレッドのエグゼキューターは、タスクを実行するための単一のワーカーのみを作成します。すべてのタスクはFIFO順に実行されます。現在のスレッドが異常な場合は、新しいスレッドが作成され、彼を置き換えてタスクを実行し続けます。その最大の特徴は、タスクの実行順序が整然としていることです。
    4. newScheduleThreadPool:定期的なタスク実行をサポートする固定長のスレッドプール。
  2. NewSingleThreadScheduledExecutorは、タスクを定期的に実行できるシングルスレッドスレッドプールです。

2.スレッドプールの共通API

1.実行する

まず、APIドキュメントのメソッドの定義を見てみましょう。
ここに画像の説明を挿入
executeメソッドは主に、スレッドプールに委任するタスクを実行するために使用されます。彼の戻り値はvoidであるため、メソッドの終了時にオブジェクトを返さず、例外が発生するとすぐに例外をスローすることを意味します。ここで、executeメソッドのソースコードを見ることができます。

public void execute(Runnable var1) {
    
    
		if (var1 == null) {
    
    
			throw new NullPointerException();
		} else {
    
    
			int var2 = this.ctl.get();
			if (workerCountOf(var2) < this.corePoolSize) {
    
    
				if (this.addWorker(var1, true)) {
    
    
					return;
				}

				var2 = this.ctl.get();
			}

			if (isRunning(var2) && this.workQueue.offer(var1)) {
    
    
				int var3 = this.ctl.get();
				if (!isRunning(var3) && this.remove(var1)) {
    
    
					this.reject(var1);
				} else if (workerCountOf(var3) == 0) {
    
    
					this.addWorker((Runnable) null, false);
				}
			} else if (!this.addWorker(var1, false)) {
    
    
				this.reject(var1);
			}

		}
	}

ソースコードのexecuteメソッドの実装手順を簡単に分析してみましょう。

  1. 現在のスレッドプール内のコアスレッドの数を取得します。この時点で存在するコアスレッドの数が設定されているコアスレッドの最大数より少ない場合、新しいコアスレッドが作成され、この時点でスレッドプールに追加されます。時間
  2. タスクタスクがキューに追加された場合でも、新しいスレッドを追加する必要があるかどうかを再判断する必要があります。この時点で、新しく作成された私はあなたが死んだか、スレッドプールが再開された可能性があるためです。したがって、必要に応じてタスクをロールバックするために、スレッドのステータスを再確認する必要があります。
  3. タスクをキューに追加できず、新しいスレッドを取得できない場合は、スレッドプールが飽和状態または停止していることを意味し、タスクは拒否されます。

2.送信する

慣例により、最初に公式ドキュメントを見てみ
ここに画像の説明を挿入
ましょう。submitメソッドはexecuteメソッドと非常によく似ており、どちらもスレッドプールに委任するタスクを実行するために使用されますが、submitメソッドはfutureを返します。実行後のタスクの結果を表すオブジェクト。操作中に例外が発生した場合、その例外はsubmitメソッドに飲み込まれ、futureオブジェクトに追加されます。もちろん、もう1つ注意すべき点があります。submitメソッドでは、2種類のパラメーターを受け取ることができます。1つは、実行可能なインターフェイスを実装するオブジェクトです。この場合、futureオブジェクトは取得できますが、結果は常にnullになります。もう1つは、呼び出し可能なインターフェイスを実装するオブジェクトです。この時点で、submitメソッドには戻り値があり、例外をスローできます。

3.実行メソッドと送信メソッドの違い

  1. execute()パラメーターRunnable; submit()パラメーター(Runnable)または(Runnable and result T)または(Callable)
  2. execute()には戻り値がなく、submit()には戻り値があります
  3. submit()Futureの戻り値はgetメソッドを呼び出し、例外をキャッチして処理できます

3つのスレッドプールツール

ここでは、実際の状況に応じて調整できる簡単なスレッドプールツールクラスの実装を提供します。

public class ThreadPoolUtil {
    
    

	// 自适应核心线程数
	private static final int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
	// 最大线程数
	private static final int maximumPoolSize = 500;
	// 存活时间
	private static final int keepAliveTime = 10;
	// 存活时间计数方式
	private static final TimeUnit timeUnit = TimeUnit.SECONDS;
	// 任务队列
	private static final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);
	// 单例模式
	private static volatile ThreadPoolExecutor threadPool;

	public static ThreadPoolExecutor getPool() {
    
    
		if (threadPool == null) {
    
    
			synchronized (ThreadPoolUtil.class) {
    
    
				if (threadPool == null) {
    
    
					threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit,
							workQueue);
				}
			}
		}
		return threadPool;

	}
	private ThreadPoolUtil() {
    
    
		super();
		// TODO Auto-generated constructor stub
	}
}

おすすめ

転載: blog.csdn.net/xiaoai1994/article/details/112100696