1.カスタムスレッドプール
1.1 サンプルコード
/**
* 自定义线程池
* <p>
* 优点:可以自定义参数
* </p>
*/
@Test
public void newThreadPoolExecutor() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
// 核心线程数
3,
// 最大线程数
5,
// 空闲线程最大存活时间
60L,
// 空闲线程最大存活时间单位
TimeUnit.SECONDS,
// 等待队列及大小
new ArrayBlockingQueue<>(100),
// 创建新线程时使用的工厂
Executors.defaultThreadFactory(),
// 当线程池达到最大时的处理策略
// new ThreadPoolExecutor.AbortPolicy() // 抛出RejectedExecutionHandler异常
new ThreadPoolExecutor.CallerRunsPolicy() // 交由调用者的线程执行
// new ThreadPoolExecutor.DiscardOldestPolicy() // 丢掉最早未处理的任务
// new ThreadPoolExecutor.DiscardPolicy() // 丢掉新提交的任务
);
// 总共5个任务
for (int i = 1; i <= 5; i++) {
int taskIndex = i;
executor.execute(() -> {
log.info("线程 " + Thread.currentThread().getName() + " 正在执行任务 " + taskIndex);
// 每个任务耗时1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
コンソールには次のように出力されます。
20:09:50.032 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-1 正在执行任务 1
20:09:50.032 [pool-1-thread-2] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-2 正在执行任务 2
20:09:50.032 [pool-1-thread-3] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-3 正在执行任务 3
20:09:51.038 [pool-1-thread-2] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-2 正在执行任务 5
20:09:51.038 [pool-1-thread-3] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-3 正在执行任务 4
2. 固定長スレッドプール
2.1 サンプルコード
/**
* 固定大小线程池
* <p>
* 优点:当任务执行较快,且任务较少时使用方便
* </p>
* <p>
* 风险:当处理较慢时,等待队列的任务堆积会导致OOM
* </p>
*/
@Test
public void newFixThreadPool() {
// 3个固定线程
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 总共5个任务
for (int i = 1; i <= 5; i++) {
int taskIndex = i;
executorService.execute(() -> {
log.info("线程 " + Thread.currentThread().getName() + " 正在执行任务 " + taskIndex);
// 每个任务耗时1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
コンソールには次のように出力されます。
20:16:27.040 [pool-1-thread-2] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-2 正在执行任务 2
20:16:27.040 [pool-1-thread-3] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-3 正在执行任务 3
20:16:27.040 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-1 正在执行任务 1
20:16:28.048 [pool-1-thread-3] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-3 正在执行任务 4
20:16:28.048 [pool-1-thread-2] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-2 正在执行任务 5
ちょうど 3 つのコア スレッドがあるため、最初の 3 つのタスクは同時に実行されます。最後の 2 つのタスクはブロッキング キューに格納され、最初の 3 つのタスクを実行するスレッドがアイドル状態になると、タスクはキューから取得されて実行されます。
2.2 ソースコード解析
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
このタイプのスレッド プールのコア スレッドの数とスレッドの最大数は指定されたパラメータであり、アイドル スレッドの生存スレッド時間は 0 ミリ秒で、待機キューは LinkedBlockingQueue を使用し、初期化サイズは Integer.MAX_VALUE です (つまり、次のようになります)。 2147483647)。
タスクの実行が遅い場合、ブロッキング キューで待機しているタスクが多数存在し、これらのタスクが大量のメモリを占有するため、OOM が発生する可能性があります。
3. シングルスレッドプール
3.1 サンプルコード
/**
* 单一线程池
* <p>
* 优势:保存任务按照提交的顺序执行
* </p>
* <p>
* 风险:当处理较慢时,等待队列的任务堆积会导致OOM
* </p>
*/
@Test
public void newSingleThreadExecutor() {
// 1个线程
ExecutorService executor = Executors.newSingleThreadExecutor();
// 总共5个任务
for (int i = 1; i <= 5; i++) {
int taskIndex = i;
executor.execute(() -> {
log.info("线程 " + Thread.currentThread().getName() + " 正在执行任务 " + taskIndex);
// 每个任务耗时1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
コンソールには次のように出力されます。
20:31:04.970 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-1 正在执行任务 1
20:31:05.974 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-1 正在执行任务 2
20:31:06.974 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-1 正在执行任务 3
20:31:07.975 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-1 正在执行任务 4
20:31:08.976 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-1 正在执行任务 5
すべてのタスクは、送信された順序で実行されます。
3.2 ソースコード解析
/**
* Creates an Executor that uses a single worker thread operating
* off an unbounded queue. (Note however that if this single
* thread terminates due to a failure during execution prior to
* shutdown, a new one will take its place if needed to execute
* subsequent tasks.) Tasks are guaranteed to execute
* sequentially, and no more than one task will be active at any
* given time. Unlike the otherwise equivalent
* {@code newFixedThreadPool(1)} the returned executor is
* guaranteed not to be reconfigurable to use additional threads.
*
* @return the newly created single-threaded Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
このタイプのスレッド プールのコア スレッドの数と最大スレッド数は両方とも 1、アイドル スレッドの生存スレッド時間は 0 ミリ秒、待機キューは LinkedBlockingQueue を使用し、初期化サイズは Integer.MAX_VALUE (つまり、2147483647) です。 )。
タスクの実行が遅い場合、ブロッキング キューで待機しているタスクが多数存在し、これらのタスクが大量のメモリを占有するため、OOM が発生する可能性があります。
4. 共有スレッドプール
4.1 サンプルコード
/**
* 共享线程池
* <p>
* 优势:当在某一时间段内任务较多,且执行较快时方便使用
* </p>
* <p>
* 风险:当处理较慢时,会创建大量的线程
* </p>
*/
@Test
public void newCachedThreadPool() {
ExecutorService executor = Executors.newCachedThreadPool();
// 总共5个任务
for (int i = 1; i <= 5; i++) {
int taskIndex = i;
executor.execute(() -> {
log.info("线程 " + Thread.currentThread().getName() + " 正在执行任务 " + taskIndex);
// 每个任务耗时1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
コンソールには次のように出力されます。
20:45:31.351 [pool-1-thread-4] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-4 正在执行任务 4
20:45:31.351 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-1 正在执行任务 1
20:45:31.351 [pool-1-thread-5] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-5 正在执行任务 5
20:45:31.358 [pool-1-thread-2] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-2 正在执行任务 2
20:45:31.359 [pool-1-thread-3] INFO com.c3stones.test.ThreadPoolTest - 线程 pool-1-thread-3 正在执行任务 3
各タスクは新しいスレッドを作成します。
4.2 ソースコード分析
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
* Calls to {@code execute} will reuse previously constructed
* threads if available. If no existing thread is available, a new
* thread will be created and added to the pool. Threads that have
* not been used for sixty seconds are terminated and removed from
* the cache. Thus, a pool that remains idle for long enough will
* not consume any resources. Note that pools with similar
* properties but different details (for example, timeout parameters)
* may be created using {@link ThreadPoolExecutor} constructors.
*
* @return the newly created thread pool
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
このタイプのスレッド プールのコア スレッドの数は 0、スレッドの最大数は Integer.MAX_VALUE (つまり、2147483647)、アイドル スレッドの最大生存時間は 60 秒、待機キューは SynchronousQueue を使用します。データを保存し、それを転送するだけです。参考: [同時プログラミング] Java Blocking Queue。
タスクが多い場合や実行が遅い場合は、大量のスレッドが作成され、OOM が発生します。
5. タイミングスレッドプール
5.1 サンプルコード
/**
* 定时线程池
* <p>
* 优点:可以定时执行某些任务
* </p>
* <p>
* 风险:当处理较慢时,等待队列的任务堆积会导致OOM
* </p>
*/
@Test
public void newScheduledThreadPool() {
// // 单一线程
// ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
// 指定核心线程数
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
executor.schedule(() -> {
log.info("3秒后开始执行,以后不再执行");
// 每个任务耗时1秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 3, TimeUnit.SECONDS);
//
// executor.scheduleAtFixedRate(() -> {
// log.info("3秒后开始执行,以后每2秒执行一次");
//
// // 每个任务耗时1秒
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }, 3, 2, TimeUnit.SECONDS);
//
// executor.scheduleWithFixedDelay(() -> {
// log.info("3秒后开始执行,以后延迟2秒执行一次");
//
// // 每个任务耗时1秒
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }, 3, 2, TimeUnit.SECONDS);
}
コンソール印刷 - 1:
21:18:46.494 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 3秒后开始执行,以后不再执行
起動後 3 秒後に実行が開始され、実行完了後は続行されません。
コンソール印刷 - 2:
21:22:47.078 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 3秒后开始执行,以后每2秒执行一次
21:22:49.075 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 3秒后开始执行,以后每2秒执行一次
21:22:51.075 [pool-1-thread-2] INFO com.c3stones.test.ThreadPoolTest - 3秒后开始执行,以后每2秒执行一次
起動後3秒後に実行され、以降2秒ごとに実行されます。
コンソール印刷 - 3:
21:28:09.701 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 3秒后开始执行,以后延迟2秒执行一次
21:28:12.705 [pool-1-thread-1] INFO com.c3stones.test.ThreadPoolTest - 3秒后开始执行,以后延迟2秒执行一次
21:28:15.707 [pool-1-thread-2] INFO com.c3stones.test.ThreadPoolTest - 3秒后开始执行,以后延迟2秒执行一次
実行は起動後 3 秒後に開始され、その後の各実行の実行時間は、タスクの所要時間に固定の遅延時間を加えたものになります。
各タスクの遅延が 2 秒に固定されていると仮定すると、最初のタスクは 3 秒で実行を開始し、タスクには 1 秒かかります。2 番目のタスクは、最初の完了から 2 秒後 (つまり 6 秒目) に実行を開始します。 、時間のかかる 2 秒、2 番目のタスクが完了した 2 秒後 (つまり 10 秒目) に 3 番目のタスクの実行が開始されます。
6. SpringBoot に非同期スレッド プールを挿入する
6.1 カスタムスレッド構成クラス
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 自定义线程池配置类
*
* @author CL
*/
@Configuration
public class TaskExecutorConfig {
/**
* 自定义任务执行器
*
* @return {@link TaskExecutor}
*/
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数,默认1
int corePoolSize = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(corePoolSize);
// 最大线程数,默认Integer.MAX_VALUE
executor.setMaxPoolSize(corePoolSize * 2 + 1);
// 空闲线程最大存活时间,默认60秒
executor.setKeepAliveSeconds(3);
// 等待队列及大小,默认Integer.MAX_VALUE
executor.setQueueCapacity(500);
// 线程的名称前缀,默认该Bean名称简写:org.springframework.util.ClassUtils.getShortName(java.lang.Class<?>)
executor.setThreadNamePrefix("custom-thread-");
// 当线程池达到最大时的处理策略,默认抛出RejectedExecutionHandler异常
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); // 抛出RejectedExecutionHandler异常
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 交由调用者的线程执行
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); // 丢掉最早未处理的任务
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); // 丢掉新提交的任务
// 等待所有任务结束后再关闭线程池,默认false
executor.setWaitForTasksToCompleteOnShutdown(true);
// 等待所有任务结束最长等待时间,默认0毫秒
executor.setAwaitTerminationSeconds(10);
// 执行初始化
executor.initialize();
return executor;
}
}
- サービスインジェクションでの使用
/**
* 示例Service
*
* @author CL
*/
public interface DemoService {
/**
* 示例方法
*
* @return {@link String}
*/
void demo();
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 示例Service实现
*
* @author CL
*/
@Slf4j
@Service
public class DemoServiceImpl implements DemoService {
@Resource
private TaskExecutor taskExecutor;
/**
* 示例方法
*/
@Override
public void demo() {
taskExecutor.execute(() -> {
log.info("线程 " + Thread.currentThread().getName() + " 正在执行Service中的方法");
});
}
}
- 非同期タスクはスレッド プールを指定します
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
/**
* 示例异步任务
*
* @author CL
*/
@Slf4j
@Component
@EnableAsync
public class DemoAsync {
/**
* 示例方法
*/
@Async(value = "taskExecutor")
public void demo() {
log.info("线程 " + Thread.currentThread().getName() + " 正在执行Async中的方法");
}
}
- タイミングタスクのスケジューリングはスレッドプールを指定します
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 自定义定时任务调度配置类
*
* @author CL
*/
@Configuration
public class SheduledConfig implements SchedulingConfigurer {
/**
* 配置定时任务
*
* @param scheduledTaskRegistrar 配置任务注册器
*/
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setScheduler(taskScheduler());
// // 第二种方式
// scheduledTaskRegistrar.setScheduler(scheduledExecutorService());
}
/**
* 自定义任务调度器
*
* @return {@link TaskScheduler}
*/
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(5);
executor.setThreadNamePrefix("custom-scheduler-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
// /**
// * 自定义任务线程池
// *
// * @return {@link ScheduledExecutorService}
// */
// @Bean
// public ScheduledExecutorService scheduledExecutorService() {
// return Executors.newScheduledThreadPool(5);
// }
}
6.2 テスト
- テストコントローラーの作成
import com.c3tones.async.DemoAsync;
import com.c3tones.service.DemoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 示例Controller
*
* @author CL
*/
@Slf4j
@RestController
public class DemoController {
@Resource
private DemoService demoService;
@Resource
private DemoAsync demoAsync;
/**
* Service示例方法
*
* @return {@link String}
*/
@RequestMapping("/service")
public void service() {
log.info("Service示例方法开始执行");
demoService.demo();
log.info("Service示例方法结束执行");
}
/**
* 异步示例方法
*
* @return {@link String}
*/
@RequestMapping("/async")
public void async() {
log.info("异步示例方法开始执行");
demoAsync.demo();
log.info("异步示例方法结束执行");
}
}
- スタートアッププロジェクト
- Service でカスタム スレッド プールをテストする
curl http://127.0.0.1:8080/service
コンソールには次のように出力されます。
2023-03-19 22:26:26.896 INFO 136568 --- [nio-8080-exec-3] com.c3tones.controller.DemoController : Service示例方法开始执行
2023-03-19 22:26:26.897 INFO 136568 --- [nio-8080-exec-3] com.c3tones.controller.DemoController : Service示例方法结束执行
2023-03-19 22:26:26.897 INFO 136568 --- [custom-thread-1] com.c3tones.service.DemoServiceImpl : 线程 custom-thread-1 正在执行Service中的方法
インターフェイスはログを同期的に出力するために呼び出され、カスタム スレッドはタスクを非同期的に実行します。
- 非同期タスクでカスタム スレッド プールをテストする
curl http://127.0.0.1:8080/async
コンソールには次のように出力されます。
2023-03-19 22:28:08.349 INFO 136568 --- [nio-8080-exec-7] com.c3tones.controller.DemoController : 异步示例方法开始执行
2023-03-19 22:28:08.355 INFO 136568 --- [nio-8080-exec-7] com.c3tones.controller.DemoController : 异步示例方法结束执行
2023-03-19 22:28:08.363 INFO 136568 --- [custom-thread-2] com.c3tones.async.DemoAsync : 线程 custom-thread-2 正在执行Async中的方法
インターフェイスはログを同期的に出力するために呼び出され、非同期スレッドはタスクを非同期的に実行します。
- スケジュールされたタスクでカスタム スレッド プールをテストする
- テストメソッドの書き込み
import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * 示例定时任务 * * @author CL */ @Slf4j @Component @EnableScheduling public class DemoScheduled { /** * 示例方法 */ @Scheduled(cron = "0/3 * * * * ? ") public void demo() { log.info("线程 " + Thread.currentThread().getName() + " 正在执行Scheduled中的方法"); } }
- サービス
コンソールを起動して印刷します。
スケジュールされたタスクは 0 秒から開始され、3 秒ごとにタスクを実行します。2023-03-19 22:30:24.002 INFO 136568 --- [tom-scheduler-3] com.c3tones.sheduled.DemoScheduled : 线程 custom-scheduler-3 正在执行Scheduled中的方法 2023-03-19 22:30:27.002 INFO 136568 --- [tom-scheduler-3] com.c3tones.sheduled.DemoScheduled : 线程 custom-scheduler-3 正在执行Scheduled中的方法 2023-03-19 22:30:30.001 INFO 136568 --- [tom-scheduler-3] com.c3tones.sheduled.DemoScheduled : 线程 custom-scheduler-3 正在执行Scheduled中的方法