文章目录
源代码:https://gitee.com/kelvin11/multithreading.git
内容提要:
- jdk ThreadPoolExecutor参数构建线程池
- 线程池配置参数说明:corePoolSize、maxPoolSize、queue、RejectExecutionHandler拒绝策略。何时填充queue
- 提交callable任务,通过future获取结果。
- 提交callable任务,使用stream().parallel()并行获取结果
- spring ThreadPoolTaskExecutor线程池配置
- 注意:异步方法不能与被调用的异步方法在同一个类中,否则将不使用多线程
- CallerRunsPolicy 由该线程调用线程运行。直接调用Runnable的run方法运行(即,当超过maxPoolSize之后,提交的请求在利用完主线程之后,会阻塞,不抛异常)
- 手动注入ThreadPoolTaskExecutor,提交callable任务,通过future获取结果
1. jdk线程池
通过idea的diagram总览jdk线程池,层次结构比较简单,我们使用ExecutorService作为接口,ThreadPoolExecutor作为实现类来创建线程池。
要注意的是:推荐自己去new ThreadPoolExecutor,而不是使用Executors.xxx来创建,后者,没有提供一些参数配置。然后配置是比较重要的,对于使用线程池也比较有用,否则容易出现一些不容易理解的错误。
![](https://i-blog.csdnimg.cn/blog_migrate/6e36a8a7d18526e6a6b7eb85ecbf0693.png)
创建的demo:
ExecutorService executorService = new ThreadPoolExecutor(2,
2,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.DiscardPolicy());
不管是jdk的ThreadPoolExecutor还是Spring的ThreadPoolTaskExecutor,对于创建线程的过程都是一样的:
当往线程池中提交新任务时,线程池主要流程如下:
核心线程数 -> 线程队列 -> 最大线程数 -> 拒绝策略
a. 如果池中任务数 < corePoolSize (核心线程数),创建新线程立即执行任务
b. 如果池中任务数 > corePoolSize,新任务放到缓存队列当中等待执行
c. 队列满,线程数量<maxPoolSize,新建线程立即执行任务
d. 队列满,线程数量>=maxPoolSize,使用拒绝策略拒绝
1.1. 超过maxPoolSize后直接丢弃策略
@Test
public void testJdkExecutorServiceRunnable() throws InterruptedException {
ExecutorService executorService = new ThreadPoolExecutor(2,
2,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 10; i++) {
int finalI = i;
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("hello" + finalI);
}
});
}
Thread.sleep(5000);
}
结果说明:
普通的Runnable线程
1 jdk线程池,ExecutorService接口
2. new ThreadPoolExecutor方式创建
3. ThreadPoolExecutor.DiscardPolicy超过 (核心线程数 + 队列数)之后,会被默默的丢弃,不会抛异常,也不会打印日志。
运行结果:
15:35:06.880 [pool-1-thread-2] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - hello1
15:35:06.880 [pool-1-thread-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - hello0
15:35:08.888 [pool-1-thread-2] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - hello2
15:35:08.888 [pool-1-thread-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - hello3
1.2. 提交Callable对象,可以获取结果
1.2.1. 非并行获取结果
@Test
public void testJdkExecutorServiceCallable() throws InterruptedException {
ExecutorService executorService = new ThreadPoolExecutor(10,
20,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.DiscardPolicy());
List<Future<String>> futureList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<String> future = executorService.submit(new MyCallable().setName(String.valueOf(i)));
futureList.add(future);
}
// 获取结果(注意这里没有使用并行流,所以,如果在获取某个结果的时候等待时间较长,可能其他线程的获取结果瞬间就会拿到,看看执行结果就会明白)
futureList.stream().forEach(e -> {
try {
log.info("执行结果[{}]:", new Object[]{e.get()});
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (ExecutionException ex) {
ex.printStackTrace();
}
});
Thread.sleep(100 * 1000);
}
执行结果1:
运行结果1:
16:16:19.662 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[0]睡了:9秒]:
16:16:19.668 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[1]睡了:0秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[2]睡了:3秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[3]睡了:1秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[4]睡了:4秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[5]睡了:1秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[6]睡了:4秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[7]睡了:3秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[8]睡了:8秒]:
16:16:19.669 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[9]睡了:7秒]:
分析:
由于获取第1个线程结果的时候,第1个线程睡了9s,导致后面都已经有结果不用等待,所以瞬间在16:16:19就可以拿到所有的结果。
执行结果2:
运行结果2:
16:17:04.278 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[0]睡了:4秒]:
16:17:04.283 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[1]睡了:0秒]:
16:17:04.283 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[2]睡了:0秒]:
16:17:04.283 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[3]睡了:1秒]:
16:17:08.278 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[4]睡了:8秒]:
16:17:08.279 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[5]睡了:6秒]:
16:17:08.279 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[6]睡了:6秒]:
16:17:08.279 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[7]睡了:8秒]:
16:17:09.274 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[8]睡了:9秒]:
16:17:09.274 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[9]睡了:9秒]:
分析:
第1个线程睡了4s,所以导致前4个线程是一起拿到结果的。
第5个线程睡了8s,所以5-8个线程是一起拿到结果的
最后2个线程,都是睡了9s,所以是一起拿到结果。
1.2.2. 并行获取结果
@Test
public void testJdkExecutorServiceCallableStreamParallel() throws InterruptedException {
ExecutorService executorService = new ThreadPoolExecutor(10,
20,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.DiscardPolicy());
List<Future<String>> futureList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<String> future = executorService.submit(new MyCallable().setName(String.valueOf(i)));
futureList.add(future);
}
// 获取结果(与上一个案例不同的,就是使用了parallel()方法,并行获取结果。)
futureList.stream().parallel().forEach(e -> {
try {
log.info("执行结果[{}]:", new Object[]{e.get()});
} catch (InterruptedException ex) {
ex.printStackTrace();
} catch (ExecutionException ex) {
ex.printStackTrace();
}
});
Thread.sleep(100 * 1000);
}
执行结果1:
执行结果1比较理想,都是睡的短的先拿到结果:
17:29:14.887 [ForkJoinPool.commonPool-worker-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[2]睡了:3秒]:
17:29:14.887 [ForkJoinPool.commonPool-worker-6] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[0]睡了:3秒]:
17:29:15.883 [ForkJoinPool.commonPool-worker-6] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[5]睡了:4秒]:
17:29:15.883 [ForkJoinPool.commonPool-worker-7] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[9]睡了:4秒]:
17:29:17.883 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[6]睡了:6秒]:
17:29:18.880 [ForkJoinPool.commonPool-worker-3] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[1]睡了:7秒]:
17:29:19.883 [ForkJoinPool.commonPool-worker-4] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[7]睡了:8秒]:
17:29:19.883 [ForkJoinPool.commonPool-worker-2] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[8]睡了:8秒]:
17:29:20.882 [ForkJoinPool.commonPool-worker-5] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[4]睡了:9秒]:
17:29:20.882 [ForkJoinPool.commonPool-worker-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[3]睡了:9秒]:
执行结果2:
运行结果2(睡了0s的,等了3s后才输出,这个应该是跟stream的parallel概念相关,后面专题看stream的时候再整理):
17:32:21.578 [ForkJoinPool.commonPool-worker-4] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[7]睡了:3秒]:
17:32:21.584 [ForkJoinPool.commonPool-worker-4] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[5]睡了:0秒]:
17:32:21.584 [ForkJoinPool.commonPool-worker-4] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[3]睡了:1秒]:
17:32:23.571 [ForkJoinPool.commonPool-worker-7] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[4]睡了:5秒]:
17:32:23.571 [ForkJoinPool.commonPool-worker-6] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[0]睡了:5秒]:
17:32:23.571 [ForkJoinPool.commonPool-worker-1] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[2]睡了:5秒]:
17:32:24.575 [ForkJoinPool.commonPool-worker-3] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[1]睡了:6秒]:
17:32:25.574 [ForkJoinPool.commonPool-worker-5] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[9]睡了:7秒]:
17:32:26.570 [main] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[6]睡了:8秒]:
17:32:27.572 [ForkJoinPool.commonPool-worker-2] INFO com.example.simple.jdk_concurrent.TestJdkConcurrent - 执行结果[Callable[8]睡了:9秒]:
结论是:不一定使用了parallel()就一定会按照睡的时间由短到长得到结果。
2. Spring线程池
2.1. 概览
下图是spring的scheduling包关于ThreadPoolTaskExecutor的概览。划线的,就是我们要关注的重点。
直接上代码,边写边看边理解。
2.2. 配置线程池
package com.example.simple.spring_scheduling;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import lombok.Data;
@Configuration
public class ThreadPoolConfig {
ThreadPoolProperties properties = new ThreadPoolProperties();
/**
* 可以通过名称注入这个配置好的线程池。
* @return
*/
@Bean(name = "taskExecutor-for-pay")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(properties.getCorePoolSize());
executor.setMaxPoolSize(properties.getMaxPoolSize());
executor.setQueueCapacity(properties.getQueueCapacity());
executor.setThreadNamePrefix(properties.getThreadNamePrefix());
// 设置线程保持活跃的时间(默认:60)
executor.setKeepAliveSeconds(properties.getKeepAliveTime());
// 当任务完成后,长时间无待处理任务时,销毁线程池
executor.setWaitForTasksToCompleteOnShutdown(properties.isWaitForTasksToCompleteOnShutdown());
executor.setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds());
// 设置任务拒绝策略
/**
* 4种
* ThreadPoolExecutor类有几个内部实现类来处理这类情况:
- AbortPolicy 丢弃任务,抛RejectedExecutionException
- CallerRunsPolicy 由该线程调用线程运行。直接调用Runnable的run方法运行。这个很有意思,复用了请求线程。后面的请求结果中,可以看到有些线程的名字是 nio-8080-exec-1 这种格式的,证明是复用了。
- DiscardPolicy 抛弃策略,直接丢弃这个新提交的任务
- DiscardOldestPolicy 抛弃旧任务策略,从队列中踢出最先进入队列(最后一个执行)的任务
* 实现RejectedExecutionHandler接口,可自定义处理器
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
@Data
class ThreadPoolProperties {
/**
* 核心线程数(默认是1):若是IO密集型,cpu核心数*2,若是cpu密集型,cpu核心数
* 核心线程会一直存活,及时没有任务需要执行
* 设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
* 注意:当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
*/
private int corePoolSize = 2;
/**
* 最大线程数,系统默认Integer.MAX_VALUE
*/
private int maxPoolSize = 4;
/**
* 允许线程空闲时间(单位:默认为秒,默认60S)
* 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
* 如果allowCoreThreadTimeout=true,则会直到线程数量=0
*/
private int keepAliveTime;
/**
* 缓冲队列大小,系统默认Integer.MAX_VALUE
* 注意:这个值肯定要改小,不然任务陡增时,都堆在队列中(队列值大),
* 核心线程数就那几个,无法很快执行队列中的任务,
* 就会延长响应时间,阻塞任务
*/
private int queueCapacity = 3;
/**
* 线程池名前缀,用于监控识别
*/
private String threadNamePrefix = "tName";
/**
* 允许核心线程超时(默认false)
*/
private boolean allowCoreThreadTimeout = false;
/**
* 当任务完成后,长时间无待处理任务时,销毁线程池
*/
private boolean waitForTasksToCompleteOnShutdown = false;
private int awaitTerminationSeconds;
}
}
跟jdk的线程池机制一样:
当往线程池中提交新任务时,线程池主要流程如下:
核心线程数 -> 线程队列 -> 最大线程数 -> 拒绝策略
a. 如果池中任务数 < corePoolSize (核心线程数),创建新线程立即执行任务
b. 如果池中任务数 > corePoolSize,新任务放到缓存队列当中等待执行
c. 队列满,线程数量<maxPoolSize,新建线程立即执行任务
d. 队列满,线程数量>=maxPoolSize,使用拒绝策略拒绝
2.3. 准备使用线程池的service
package com.example.simple.spring_scheduling;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
/**
* @Async失效情况: 异步方法使用static修饰
* 2.异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
* 异步方法不能与被调用的异步方法在同一个类中
* 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
* 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
*/
@Service
@EnableAsync
@Slf4j
public class ThreadPoolService {
@Async("taskExecutor-for-pay")
public void process(int i) throws InterruptedException {
log.info(Thread.currentThread().getName() + "开始处理第【" + i + "】个请求");
Thread.sleep(2 * 1000);
log.info("第【{}】个请求处理结束", i);
}
public void F2() throws InterruptedException {
for (int i = 0; i < 15; i++) {
// 想要调起15个线程,可以吗?
process(i);
}
}
}
2.4. 准备controller
package com.example.simple.controller;
import com.example.simple.jdk_concurrent.MyCallable;
import com.example.simple.spring_scheduling.ThreadPoolService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @ClassName SampleController
* @Description
* @Author liukun
* @Date 2022/6/11 17:19
*/
@RestController
@Slf4j
public class SampleController {
@Resource
ThreadPoolService threadPoolService;
@Resource(name = "taskExecutor-for-pay")
ThreadPoolTaskExecutor threadPoolTaskExecutor;
/**
* spring ThreadPoolTaskExecutor的使用
* @param fun
* @return
* @throws InterruptedException
*/
@RequestMapping("/spring/threads/{function}")
public String testSpringThreadPoolTaskExecutor1(@PathVariable("function") String fun) throws InterruptedException, ExecutionException {
if (fun.equals("f2")) {
// 在threadPoolService.F2()方法内,循环调用10次process()方法,process()方法有异步注解,F2方法签名没有异步注解
threadPoolService.F2();
}
else if (fun.equals("process")) {
// process()方法签名有异步注解,循环调起15次。
log.info("15个process准备调用。");
for (int i = 0; i < 15; i++) {
log.info("准备提交第【{}】个请求", i);
threadPoolService.process(i);
}
log.info("15个process已经发到线程池了。主请求结束。");
}
else if (fun.equals("callable")) {
// 向 threadPoolTaskExecutor 提交Callable任务。
Future<String> future = threadPoolTaskExecutor.submit(new MyCallable().setName("SpringCallable"));
// 由于执行了 future.get(), 所以请求会等到get到结果之后才能收到响应。
log.info("线程执行的结果:" + future.get());
}
log.info("开始写响应给请求方。");
return "hello world";
}
}
2.5. 调用结果分析
http://localhost:8080/spring/threads/f2
先说结论:
public void F2() throws InterruptedException {
for (int i = 0; i < 15; i++) {
// 想要调起15个线程,可以吗?
process(i);
}
}
这段代码,看起来是想调起15个process,而process,是用异步注解的,但是实际并没有使用到线程池,打印的日志里,使用的线程都是请求线程。
调用结果:
2022-06-11 19:58:40.274 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【0】个请求
2022-06-11 19:58:42.276 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【0】个请求处理结束
2022-06-11 19:58:42.279 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【1】个请求
2022-06-11 19:58:44.283 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【1】个请求处理结束
2022-06-11 19:58:44.283 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【2】个请求
2022-06-11 19:58:46.287 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【2】个请求处理结束
2022-06-11 19:58:46.287 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【3】个请求
2022-06-11 19:58:48.288 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【3】个请求处理结束
2022-06-11 19:58:48.288 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【4】个请求
2022-06-11 19:58:50.293 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【4】个请求处理结束
2022-06-11 19:58:50.293 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【5】个请求
2022-06-11 19:58:52.298 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【5】个请求处理结束
2022-06-11 19:58:52.299 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【6】个请求
2022-06-11 19:58:54.303 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【6】个请求处理结束
2022-06-11 19:58:54.304 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【7】个请求
2022-06-11 19:58:56.308 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【7】个请求处理结束
2022-06-11 19:58:56.308 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【8】个请求
2022-06-11 19:58:58.309 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【8】个请求处理结束
2022-06-11 19:58:58.309 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【9】个请求
2022-06-11 19:59:00.312 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【9】个请求处理结束
2022-06-11 19:59:00.312 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【10】个请求
2022-06-11 19:59:02.313 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【10】个请求处理结束
2022-06-11 19:59:02.314 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【11】个请求
2022-06-11 19:59:04.314 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【11】个请求处理结束
2022-06-11 19:59:04.315 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【12】个请求
2022-06-11 19:59:06.318 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【12】个请求处理结束
2022-06-11 19:59:06.318 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【13】个请求
2022-06-11 19:59:08.322 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【13】个请求处理结束
2022-06-11 19:59:08.323 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【14】个请求
2022-06-11 19:59:10.323 INFO 11561 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【14】个请求处理结束
2022-06-11 19:59:10.324 INFO 11561 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 开始写响应给请求方。
在继续看,在controller中循环调用15次process,能使用到线程池吗?
else if (fun.equals("process")) {
// process()方法签名有异步注解,循环调起15次。
log.info("15个process准备调用。");
for (int i = 0; i < 15; i++) {
log.info("准备提交第【{}】个请求", i);
threadPoolService.process(i);
}
log.info("15个process已经发到线程池了。主请求结束。");
}
结果:是可以的,并发可以有效减少任务的执行时间。
http://localhost:8080/spring/threads/process
调用结果:
2022-06-11 20:01:51.445 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 15个process准备调用。
2022-06-11 20:01:51.445 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【0】个请求
2022-06-11 20:01:51.449 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【1】个请求
2022-06-11 20:01:51.450 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【2】个请求
2022-06-11 20:01:51.450 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【3】个请求
2022-06-11 20:01:51.450 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【4】个请求
2022-06-11 20:01:51.450 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【5】个请求
2022-06-11 20:01:51.450 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【6】个请求
2022-06-11 20:01:51.451 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【7】个请求
2022-06-11 20:01:51.455 INFO 11619 --- [ tName1] c.e.s.s.ThreadPoolService : tName1开始处理第【0】个请求
2022-06-11 20:01:51.455 INFO 11619 --- [ tName4] c.e.s.s.ThreadPoolService : tName4开始处理第【6】个请求
2022-06-11 20:01:51.455 INFO 11619 --- [ tName2] c.e.s.s.ThreadPoolService : tName2开始处理第【1】个请求
2022-06-11 20:01:51.455 INFO 11619 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【7】个请求
2022-06-11 20:01:51.455 INFO 11619 --- [ tName3] c.e.s.s.ThreadPoolService : tName3开始处理第【5】个请求
2022-06-11 20:01:53.459 INFO 11619 --- [ tName1] c.e.s.s.ThreadPoolService : 第【0】个请求处理结束
2022-06-11 20:01:53.459 INFO 11619 --- [ tName2] c.e.s.s.ThreadPoolService : 第【1】个请求处理结束
2022-06-11 20:01:53.460 INFO 11619 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【7】个请求处理结束
2022-06-11 20:01:53.459 INFO 11619 --- [ tName4] c.e.s.s.ThreadPoolService : 第【6】个请求处理结束
2022-06-11 20:01:53.460 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【8】个请求
2022-06-11 20:01:53.460 INFO 11619 --- [ tName2] c.e.s.s.ThreadPoolService : tName2开始处理第【3】个请求
2022-06-11 20:01:53.460 INFO 11619 --- [ tName1] c.e.s.s.ThreadPoolService : tName1开始处理第【2】个请求
2022-06-11 20:01:53.460 INFO 11619 --- [ tName3] c.e.s.s.ThreadPoolService : 第【5】个请求处理结束
2022-06-11 20:01:53.460 INFO 11619 --- [ tName4] c.e.s.s.ThreadPoolService : tName4开始处理第【4】个请求
2022-06-11 20:01:53.460 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【9】个请求
2022-06-11 20:01:53.461 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【10】个请求
2022-06-11 20:01:53.462 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【11】个请求
2022-06-11 20:01:53.462 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【12】个请求
2022-06-11 20:01:53.462 INFO 11619 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : http-nio-8080-exec-1开始处理第【12】个请求
2022-06-11 20:01:53.462 INFO 11619 --- [ tName5] c.e.s.s.ThreadPoolService : tName5开始处理第【11】个请求
2022-06-11 20:01:55.462 INFO 11619 --- [ tName1] c.e.s.s.ThreadPoolService : 第【2】个请求处理结束
2022-06-11 20:01:55.462 INFO 11619 --- [ tName4] c.e.s.s.ThreadPoolService : 第【4】个请求处理结束
2022-06-11 20:01:55.462 INFO 11619 --- [ tName2] c.e.s.s.ThreadPoolService : 第【3】个请求处理结束
2022-06-11 20:01:55.463 INFO 11619 --- [ tName5] c.e.s.s.ThreadPoolService : 第【11】个请求处理结束
2022-06-11 20:01:55.463 INFO 11619 --- [ tName2] c.e.s.s.ThreadPoolService : tName2开始处理第【10】个请求
2022-06-11 20:01:55.463 INFO 11619 --- [ tName4] c.e.s.s.ThreadPoolService : tName4开始处理第【9】个请求
2022-06-11 20:01:55.463 INFO 11619 --- [ tName1] c.e.s.s.ThreadPoolService : tName1开始处理第【8】个请求
2022-06-11 20:01:55.463 INFO 11619 --- [nio-8080-exec-1] c.e.s.s.ThreadPoolService : 第【12】个请求处理结束
2022-06-11 20:01:55.464 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【13】个请求
2022-06-11 20:01:55.464 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 准备提交第【14】个请求
2022-06-11 20:01:55.464 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 15个process已经发到线程池了。主请求结束。
2022-06-11 20:01:55.464 INFO 11619 --- [nio-8080-exec-1] c.e.simple.controller.SampleController : 开始写响应给请求方。
2022-06-11 20:01:57.464 INFO 11619 --- [ tName4] c.e.s.s.ThreadPoolService : 第【9】个请求处理结束
2022-06-11 20:01:57.464 INFO 11619 --- [ tName2] c.e.s.s.ThreadPoolService : 第【10】个请求处理结束
2022-06-11 20:01:57.464 INFO 11619 --- [ tName1] c.e.s.s.ThreadPoolService : 第【8】个请求处理结束
2022-06-11 20:01:57.464 INFO 11619 --- [ tName4] c.e.s.s.ThreadPoolService : tName4开始处理第【13】个请求
2022-06-11 20:01:57.464 INFO 11619 --- [ tName1] c.e.s.s.ThreadPoolService : tName1开始处理第【14】个请求
2022-06-11 20:01:59.465 INFO 11619 --- [ tName4] c.e.s.s.ThreadPoolService : 第【13】个请求处理结束
2022-06-11 20:01:59.465 INFO 11619 --- [ tName1] c.e.s.s.ThreadPoolService : 第【14】个请求处理结束
最后就是简单了解一下,是否可以向threadPoolTaskExecutor提交callable任务?就是说在线程执行完成后获取到结果?
else if (fun.equals("callable")) {
// 向 threadPoolTaskExecutor 提交Callable任务。
Future<String> future = threadPoolTaskExecutor.submit(new MyCallable().setName("SpringCallable"));
// 由于执行了 future.get(), 所以请求会等到get到结果之后才能收到响应。
log.info("线程执行的结果:" + future.get());
}
上面threadPoolTaskExecutor是通过名称注入到controller中的:
@Resource(name = "taskExecutor-for-pay")
ThreadPoolTaskExecutor threadPoolTaskExecutor;
执行结果也简单,就是在拿到结果之后,写响应给调用方。
当然,如果我们没有显示的调用get()方法,那就会直接写响应了。
2022-06-12 23:21:37.152 INFO 19311 --- [nio-8080-exec-4] c.e.simple.controller.SampleController : 线程执行的结果:Callable[SpringCallable]睡了:1秒
2022-06-12 23:21:37.155 INFO 19311 --- [nio-8080-exec-4] c.e.simple.controller.SampleController : 开始写响应给请求方。