目录
线程池执行顺序
核心线程数(CorePoolSize) ---> 全部被占用 ----> 剩余线程任务放入阻塞队列 ----> 队列全部被占用 ----> 根据最大线程数(MaxPoolSize)扩充线程数量 ----> 全部线程和队列都被占用 ----> 执行拒绝策略(舍弃并报错,舍弃但不报错,丢给调用方的线程执行等。。)
线程池配置策略
首先要了解自己本机或服务器的物理核心数,线程数设置太大会导致线程不停地切换,并不是真正意义上的并行。
(1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换,可以将阻塞队列设置大一点,最大线程数与核心线程数相当,这样可以避免创建太多的线程。 (2)并发不高、任务执行时间长的业务要区分开看: a、假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务 b、假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和 (1) 一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
Spring线程池的配置类:
举例了两种不同的线程池策略,在注入时需要标明bean的name属性,即可调用具体的线程池。
注:不同业务不要混用同一种线程池策略,应该单独配置适合业务的配置,
同一台服务器中的微服务的线程池总数不能太多,
最大线程数必须大于等于核心线程数,否则会报错。
package com.example.jucdemo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
// 低并发,IO耗时长的策略
@Bean(name = "myExecutor")
public ThreadPoolTaskExecutor taskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 较大核心线程数
executor.setCorePoolSize(20);
executor.setMaxPoolSize(100);
executor.setKeepAliveSeconds(60);
executor.setQueueCapacity(30);
// 设置线程池内线程名称的前缀
executor.setThreadNamePrefix("IO-threadPool-");
//设置任务的拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//初始化
executor.initialize();
return executor;
}
// 高并发,耗时短的策略
@Bean(name = "concurrencyExecutor")
public ThreadPoolTaskExecutor concurrencyExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 小核心线程数
executor.setCorePoolSize(5);
executor.setMaxPoolSize(5);
executor.setKeepAliveSeconds(2);
// 大阻塞队列
executor.setQueueCapacity(500);
// 设置线程池内线程名称的前缀
executor.setThreadNamePrefix("concurrency-threadPool-");
// 设置任务的拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
// 初始化
executor.initialize();
return executor;
}
}
Spring有、无返回值的异步调用示例
无返回值的较为简单,直接把耗时的任务设置为异步方法,丢给线程池执行。
有返回值的需要异步方法返回的是 Future<> 接口,
一般return new AsyncResult<>(result),将真正的结果传入。AsyncResult实现了ListenableFuture接口,而后者又继承了Future接口。
在需要其结果时调用 future.get()方法即可将结果取出,get()方法会阻塞当前线程,等待异步方法的返回值。
直接用 threadPoolTaskExecutor.excute(Runnable task)或.submit(Runnable task)来执行无返回值的异步方法。
用 .submit(Callable task) 方法执行有返回值的异步方法。这里的Runnable和Callable都是函数式接口,只有一个抽象方法,因此建议使用lamda表达式写法更为简洁。
具体见下面的代码示例。
自定义的异步方法类代码:
其中的 @Async 注解可在类或方法上标注,分别用来标记一个类下的所有方法为异步方法,或标记一个具体的方法为异步方法,可传入具体使用的线程池的bean的名字。
package com.example.jucdemo.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
@Service
public class MyAsyncTest {
// 无返回值
@Async("myExecutor")
public void execute(){
System.out.println("开始执行异步任务--1");
try {
for (int i=0;i<10;i++){
System.out.println("异步任务--1:第"+(i+1)+"次");
Thread.sleep(100);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("异步任务--1:最终完成");
}
// 有返回值
@Async("myExecutor")
public Future<Integer> algorithm1(Integer var1,Integer var2) throws InterruptedException {
Thread.sleep(1000);
return new AsyncResult<>(var1+var2);
}
@Async("myExecutor")
public Future<Integer> algorithm2(Integer var1,Integer var2) throws InterruptedException {
Thread.sleep(2000);
return new AsyncResult<>(var1 * var2);
}
@Async("myExecutor")
public Future<String> circulation(int i) throws InterruptedException {
Thread.sleep(500);
return new AsyncResult<>(String.format("结果一:%s",i));
}
}
测试类代码
package com.example.jucdemo;
import com.example.jucdemo.service.MyAsyncTest;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@SpringBootTest
class JucDemoApplicationTests {
@Resource(name = "myExecutor")
ThreadPoolTaskExecutor executor;
@Resource
MyAsyncTest asyncTest;
// 无返回值的异步调用
@Test
public void executeTest() throws InterruptedException {
System.out.println("主线程开始执行");
// 方式一:调用 MyAsyncTest 类中定义的异步方法
asyncTest.execute();
// 方式二:直接调用线程池的execute方法
executor.execute(() -> {
System.out.println("开始执行异步任务--2");
for (int i=0;i<10;i++){
try {
System.out.println("异步任务--2:第"+(i+1)+"次");
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("异步任务--2:最终完成");
});
//由于单元测试主线程结束进程直接结束,所以主线程需要sleep等待异步方法
Thread.sleep(2000);
System.out.println("进程结束");
}
// 带返回值的异步调用
@Test
public void submitTest() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
// 调用异步算法1
Future<Integer> param1 = executor.submit(() -> {
System.out.println("正在计算param1");
Thread.sleep(3000);
return 1;
});
// 调用异步算法2
Future<Integer> param2 = executor.submit(() -> {
System.out.println("正在计算param2");
Thread.sleep(5000);
return 2;
});
// 等待接收异步算法的结果
int result = param1.get() + param2.get();
long end = System.currentTimeMillis();
long time = end - start;
System.out.println("总共耗时:" + time);
System.out.println("计算结果为:" + result);
}
// 带返回值的异步方法的调用
@Test
public void asyncResultTest() throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
System.out.println("开始:");
int var1 = 2;
int var2 = 3;
Future<Integer> result1 = asyncTest.algorithm1(var1, var2);
Future<Integer> result2 = asyncTest.algorithm2(var1, var2);
System.out.println("结果二" + result2.get());
long end1 = System.currentTimeMillis();
System.out.println("结果一:" + result1.get());
long end2 = System.currentTimeMillis();
System.out.println("时间一:" + (end1 - start));
System.out.println("时间二:" + (end2 - start));
}
@Test
public void circulationTest() throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
List<Future<String>> futures = new ArrayList<>();
List<String> resultList = new ArrayList<>();
//批量调用异步方法
for (int i=1;i<=10;i++){
Future<String> f = asyncTest.circulation(i);
futures.add(f);
}
//全部执行完毕后统一获取
for (Future<String> future : futures) {
resultList.add(future.get());
}
long end = System.currentTimeMillis();
System.out.println("结果为:" + resultList);
System.out.println("耗时:" + (end - start));
}
}
常见问题:
以下会使@Async注解失效
一、异步方法不能使用static修饰
二、异步类没有使用@Component注解(或其他注解如@Service)导致spring无法扫描到异步类
三、@Async标注的异步方法与调用其的方法不能在同一个类中
四、类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
五、使用SpringBoot框架必须在启动类或配置类中增加@EnableAsync注解
六、在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效
参考文章:
线程池的各种使用场景_星空dream的博客-CSDN博客_线程池的应用场景
@EnableAsync@Async使用总结 - 在贝加尔湖畔 - 博客园 (cnblogs.com)
SpringBoot异步任务, 以及带返回值的异步任务(@Async 不起作用的原因)_晴天小哥哥的博客-CSDN博客_springboot方法异步