実際のプロジェクトでは、非常に短い実行時間で非常に多くのリクエストを処理する場合があります。このとき、リクエストごとに新しいスレッドを作成すると、パフォーマンスのボトルネックが発生する可能性があります。スレッドの作成と破棄の時間はタスクの実行時間よりも長くなる可能性があるため、システムのパフォーマンスが大幅に低下します。
JDK1.5は、スレッドプールのサポートを提供します。これは、スレッド再利用テクノロジーを使用して頻繁なスレッド作成を排除し、多数の同時要求を処理する目的を達成します。スレッドプールの原理は、優れた操作効率で「ThreadPool」を作成し、プール内のスレッドオブジェクトの作成と破棄を管理することです。プールを使用する場合、特定のタスクのみを実行する必要があり、スレッドオブジェクトの処理はプールにカプセル化されます。 。以下は、スレッドプールの簡単な例です。
public static void main(String[] args) throws IOException {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 3, 30, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
tpe.execute(() -> {
System.out.println("我在线程池中" + Thread.currentThread().getName() + "执行");
});
}
上記の例では、ThreadPoolExecutorを使用してスレッドプールを作成します。以下では、最初にThreadPoolExecutorクラスの階層構造図を確認します。
上記のクラス階層図から、Executorがスレッドプール全体のルートインターフェイスであることがわかります。実際、このインターフェースにはメソッドが1つしかありません。このメソッドは、将来のある時点で特定のコマンド(スレッドタスク)を指定するために使用されます。タスクは、新しいスレッド、スレッドプールに存在するスレッド、または呼び出されているスレッドで実行できます。これは、Executorを実装するクラスによって決定されます。Executorの定義は次のとおりです。
public interface Executor {
void execute(Runnable command);
}
ExecutorServiceインターフェイスはExecutorインターフェイスを継承します。Executorインターフェイスはスレッドプール実行タスクのメソッドのみを定義し、ExecutorServiceはスレッドプールに関するメソッドを追加します。これらのメソッドがあることを知っておく必要があるだけです。これらのメソッドは次の実装クラスで実装されます。詳細に説明してください。以下は、ExecutorServiceインターフェースの定義です。
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
AbstractExecutorServiceは、ExecutorServiceを直接継承または実装します。AbstractExecutorServiceは、ExecutorServiceの抽象実装クラスです。ExecutorServiceおよびExecutorの一部の機能を実装する抽象クラスです。その実際の実装はThreadPoolExecutorです。以下では、スレッドプールThreadPoolExecutorの使用について詳しく説明します。
Executorは単なるインターフェースであり、仕様であり、関数を実装していません。指定された関数を完了するには、ThreadPoolExecutorクラスを実装する必要があります。
ThreadPoolExecutorは、ThreadPoolExecutorインスタンスを作成するためのいくつかの構築メソッドを提供します。以下はそれらの1つです。他の構築メソッドは、最終的にこの構築メソッドを呼び出します。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
コアプールのサイズであるアイドルスレッドを含む、上記のパラメーターでcorePoolSizeスレッドプールに保存されたスレッドの数。maximumPoolSizeは、プールで許可されるスレッドの最大数です。keepAliveTimeスレッド数がcorePoolSizeより大きい場合、アイドルスレッドはkeepAliveTimeを超えると削除されます。ユニットkeepAliveTimeの時間単位。workQueueが実行される前にタスクを保存するために使用されるキュー。最も一般的に使用されるのは、LinkedBlockQueue、ArrayBlockQueue、およびSynchronousQueueです。ThreadFactoryはスレッドファクトリを表し、ハンドラは拒否後の処理を表します。以下に、ThreadPoolExecutorの実行プロセスを紹介します。
ThreadPoolExecutorは、異なるキューを使用して異なる実行プロセスを持ちます。例としてLinkedBlockQueueとSynchronousQueueを取り上げます。パラメーターを簡略化するために、コードを使用して、Aは実行されるタスクを表し、BはcorePoolSizeを表し、CはmaximumPoolSizeを表し、EはLinkedBlockQueueキューを表し、FはLinkedBlockQueueキューを表します。 SynchronousQueueキュー、GはkeepLiveTimeを表します。
上記のフローチャートに示すように、A <= Bの場合、実行されるタスクの数はcorePoolSize以下であり、スレッドプールはタスクをすぐに実行するスレッドを作成します。タスクはキューに追加されません。
A> B && A <= Cの場合、つまり実行されるタスクの数がcorePoolSizeより大きくmaximumPoolSizeより小さい場合、2つの異なるキューの処理方法は異なります。LinkedBlockQueueはABタスクをLinkedBlockQueueキューに入れ、前のタスクが実行されるまで待機します。 、実行のためにキューからタスクを取り出します。SynchronousQueueは、すべてのタスクを実行するための新しいスレッドを作成します。デモコードは次のとおりです。
public class LinkedQueueThreadPool2 {
public static void main(String[] args) {
Runnable runnable = new TpeRunnable("blockQueue");
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
//要执行的任务数量大于corePoolSize,但小于maximumPoolSize
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
System.out.println(tpe.getPoolSize());//3
System.out.println(tpe.getActiveCount());//3
System.out.println(tpe.getQueue().size());//2,两个任务放到等待列表,相当于C和G参数无效。poolSize数量一直小于等于corePoolSize。
}
}
public class SynchronousQueueThreadPool2 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new TpeRunnable("SynchQueue");
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
//要执行的任务数量大于corePoolSize,但小于maximumPoolSize
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
//迅速创建新线程并执行任务
System.out.println(tpe.getPoolSize());//5
System.out.println(tpe.getActiveCount());//5
System.out.println(tpe.getQueue().size());//0
Thread.sleep(10000);
System.out.println(tpe.getPoolSize());//3,5秒之后空闲线程清除,只剩下与corePoolSize数量一样的线程数
}
}
A> Cの場合、つまり実行されるタスクの数がmaximumPoolSizeより大きい場合、2つの異なるキューの処理方法は異なります。LinkedBlockQueueはABタスクをLinkedBlockQueueキューに入れ、SynchronousQueueはjava.util.concurrentを直接スローします。 .RejectedExecutionException例外。以下は、この状況のコード例です。
public class LinkedQueueThreadPool3 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new TpeRunnable("blockQueue");
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
//该处理方式与上面的类似,大于3的任务会加入到队列等待执行
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
System.out.println(tpe.getPoolSize());//3
System.out.println(tpe.getActiveCount());//3
System.out.println(tpe.getQueue().size());//3
Thread.sleep(10000);
System.out.println(tpe.getPoolSize());//3
}
}
public class SynchronousQueueThreadPool3 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new TpeRunnable("SynchQueue");
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
tpe.execute(runnable);
System.out.println(tpe.getPoolSize());
System.out.println(tpe.getActiveCount());
System.out.println(tpe.getQueue().size());
Thread.sleep(10000);
System.out.println(tpe.getPoolSize());
//该示例会抛出异常,当要执行的线程数大于maximumPoolSize时,并且使用SynchronousQueue队列时,会抛出异常
}
}
上記の実行フローをより明確に表現するために、次の図に示すように、スレッドプールの実行フローを示すフローチャートを作成しました。
上記では、さまざまなキューでThreadPoolExecutorを使用する方法を示しました。以下では、ThreadPoolExecutorを閉じる方法と、スレッドプールを正しく閉じる方法を紹介します。ThreadPoolExecutorは、スレッドプールを閉じるためのshutdown()、shutdownNow()、およびisShutdown()の3つの方法を提供します。
isShutDown()は、スレッドプールがシャットダウンされているかどうかを判別します。shutdown()メソッドが呼び出されている限り、trueを返します。
shutdown()メソッドの機能は、現在未完了のスレッドを新しいタスクを実行せずに実行し続けることです。shutdown()メソッドを呼び出した後、メインスレッドはすぐに終了し、スレッドプールは実行を継続します。すべてのタスクが実行されるまで停止します。shutdown()メソッドが呼び出されない場合、スレッドプールはいつでも新しいタスクを実行するために永久に保持されます。Shutdown()メソッドは非ブロッキングです。shutdown()メソッドの実行後に新しいタスクを追加すると、RejectedExecutionExceptionがスローされます。
ShutdownNow()メソッドはすべてのタスクを中断し、InterruptedExceptionをスローします。スレッドでThread.currentThread()。isInterrupted()== trueを使用して現在のスレッドの中断ステータスを判別する場合、未実行のタスクは実行されません。そのような判断がない場合、プール内で実行されているスレッドは実行が完了するまで実行されず、実行されていないスレッドは実行されず、実行キューから削除されます。shutdownNow()メソッドの実行後に新しいタスクを追加すると、RejectedExecutionExceptionがスローされます。
先ほど、ThreadPoolExecutorを大まかに紹介しました。その構築には多くのパラメータが含まれていることがわかります。構築メソッドを使用してThreadPoolExecutorインスタンスを作成する場合、構築メソッドで複数のパラメータを渡す必要があります。これは使用するのが非常に面倒ですが、公式JDKは、スレッドプールを作成するためのツールクラスExecutorsを提供します。
実行者は、newCachedThreadPool()メソッドを使用して、無制限のスレッドプールを作成できます。また、スレッドによって自動的にリサイクルできます。いわゆるボーダレスとは、スレッドプール内のスレッドの最大数がInteger.MAX_VALUEであることを意味します。newCachedThreadPool()メソッドとnewCachedThreadPool(ThreadFactory threadFactory)メソッドを使用して、ボーダレススレッドを作成できます。次の例では、newCachedThreadPool()を使用しています。
ExecutorService tpe = Executors.newCachedThreadPool();
ExecutorService tpe = Executors.newCachedThreadPool(new MyThreadFactory());
public class MyThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
実行者は、newFixedThreadPool(int nThreads)メソッドとnewFixedThreadPool(int nThreads、ThreadFactory threadFactory)メソッドを使用して、制限付きスレッドプールを作成できます。スレッドプールの数は、メソッドで指定されたnThreadsの値です。
ExecutorService es = Executors.newFixedThreadPool(2);
実行者は、newSingleThreadPool(int nThreads)メソッドとnewSingleThreadPool(int nThreads、ThreadFactory threadFactory)メソッドを使用して、1つのスレッドのみでスレッドプールを作成できます。スレッドプールの数は固定値であり、1つだけです。
ExecutorService es = Executors.newSingleThreadExecutor();
エグゼキュータは、スレッドプールの作成を大幅に容易にします。実際、スレッドプールインスタンスは、ThreadPoolExecutorの構築メソッドを介して内部的に初期化されます。次の例は、シングルスレッドスレッドプールを作成するためのコードです。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}