スレッドプール
1.スレッドプールの消費
Javaはオブジェクトを作成し、ヒープメモリにメモリ空間を割り当てます。スレッドを作成するには、オペレーティングシステムカーネルのAPIを呼び出す必要があります。その後、オペレーティングシステムがスレッドに一連のリソースを割り当てる必要があります。これは非常にコストがかかります。
**スレッドは重いオブジェクトなので、頻繁に作成したり破棄したりしないでください。**したがって、スレッドプールスキームは適切です。
2.スレッドプールは実際にはプロデューサーコンシューマーモデルです
スレッドプール、最初に考えるのはリソースのプールです。つまり、リソースを使用し、リソースを申請し、リソースを解放するまで使用する必要がある場合ですが、スレッドプールはこのようなものではありません。
スレッドプールは実際にはプロデューサー/コンシューマーモデルであり、スレッドのコンシューマーはプロデューサー(プロダクションタスクタスク)、スレッドプール自体はコンシューマー(コンシューマタスクタスク)です。
2.1手動でスレッドプールを実装する
原則:ブロックキューがスレッドプールに追加され、実行するタスクRunableが格納されます。指定された数のスレッドスレッドがスレッドプール構築メソッドで作成され、スレッドプールリストに配置されます。ブロッキングキューからタスクを削除して実行します。
//简化的线程池,仅用来说明工作原理
class MyThreadPool{
//阻塞队列存放Runable任务
BlockingQueue<Runnable> workQueue;
//线程池列表,用来保存线程
List<WorkerThread> threads = new ArrayList<>();
// 构造方法
MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue){
this.workQueue = workQueue;
// 创建工作线程
for(int idx=0; idx<poolSize; idx++){
WorkerThread work = new WorkerThread();
work.start();//启动线程池中的所有线程
threads.add(work);//把线程添加到线程池列表中
}
}
// 提交任务
void execute(Runnable command){
workQueue.put(command);
}
// 工作线程负责消费任务,并执行任务
class WorkerThread extends Thread{
public void run() {
//循环取任务并执行
while(true){ ①
Runnable task = workQueue.take();
task.run();
}
}
}
}
/** 下面是使用示例 **/
// 创建有界阻塞队列
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(2);
// 创建线程池
MyThreadPool pool = new MyThreadPool(10, workQueue);
// 提交任务
pool.execute(()->{System.out.println("hello");});
3. Javaのスレッドプールの使用方法
Javaコンカレントパッケージで提供されるスレッドプールは、上記で実装したスレッドプールよりもはるかに優れています。コアはThreadPoolExecutorです。
3.1 ThreadPoolExecutor構築メソッド
以下はThreadPoolExecutorのコンストラクターです。
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
スレッドプールのパラメーターを1つずつ紹介します。
-
corePoolSize:スレッドプールに保持されるスレッドの最小数。プログラムに実行するタスクがない場合でも、いくつかのスレッドが実行を待機している必要があります。現在のスレッドがまだ残っている場合は、それに応じてライブスレッドの数が減少します。
- allowCoreThreadTimeOut(ブール値)メソッド:プロジェクトが非常にビジーな場合、最小スレッドmaximumPoolSizeを残さずにすべてのスレッドを撤回できます。
-
maximumPoolSize:スレッドプールで作成できるスレッドの最大数を表します。現在残っているスレッドが少ない場合は、それに応じてスレッド数を増やします。
-
keepAliveTime&unit:上記の2つのパラメーターは、一定期間のスレッドの実行に応じて、スレッドを増減します。keepAliveTime&unitは、この時間を定義します。スレッドがアイドル状態になっている場合keepAliveTime&unitと、スレッドの数> corePoolSize、アイドルスレッドが回復されます。
-
workQueue:ワークキュー。これは、上記のスレッドプールを手動で実装するキューと同義であり、実行するタスクを格納します。
-
threadFactory:このカスタムに従ってスレッドを作成できます。たとえば、意味のある名前をスレッドに割り当てます。
-
ハンドラー:このカスタムタスク拒否戦略を使用できます。スレッドプール内のすべてのスレッドが実行中で、作業キュー内のタスクがいっぱいになると、スレッドプールはタスクの受け入れを拒否し、タスクの送信時に拒否できます。ストラテジーはハンドラーで指定できます。
ThreadPoolExecutorが提供する4つの戦略:- CallerRunsPolicy:タスクを送信したスレッドは、タスク自体を実行します。
- AbortPolicy:デフォルトの拒否ポリシーはRejectedExecutionExceptionをスローします。
- DiscardPolicy:例外をスローせずにタスクを直接破棄します。
- DiscardOldestPolicy:最も古いタスクを破棄すると、実際にはワークキューに入った最も古いタスクが破棄され、新しいタスクがワークキューに追加されます。
3.2スレッドプールの使用に関する注意
- 開発ではThreadPoolExecutorを使用してください。Executorsを使用してスレッドプールをすばやく作成しないでください。
- 境界キューを使用することを強くお勧めします。
- デフォルトの拒否戦略、例外のスロー、慎重な使用、
- 例外処理:ThreadPoolExecutorオブジェクトのexecutorメソッドなど、タスクを送信します。タスクの実行中に例外が発生すると、スレッドが終了し、タスクは異常になりますが、例外通知は受信されないため、すべてのタスクが正常に実行されていると考えられます。したがって、最も安全な方法は、例外をキャッチすることです。
try {
//业务逻辑
} catch (RuntimeException x) {
//按需处理
} catch (Throwable x) {
//按需处理
}