关于多线成调用可能大家用的比较多的是JDK的多线程,springboot1.5+,项目框架中集成了异步多线程操作配置,在这里和大家分享一下springboot的异步多线程注解使用,先一步一步来以代码的形式讲解大家可能会遇到的问题。
一:创建方法,然后在方法上添加@Async注解,然后还需要在@SpringBootApplication启动类或者@configure注解类上 添加注解@EnableAsync启动多线程注解,@Async就会对标注的方法开启异步多线程调用,注意,这个方法的类一定要交给spring容器来管理
@Component
public class Test {
//注意这个多线程方法的类一定要加@Component注解,拿给spring容器管理
@Async
public void doTaskThree(int i) {
long start = System.currentTimeMillis();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("第00" + i + "完成任务,耗时:" + (end - start) + "毫秒,线成名为::" + Thread.currentThread().getName());
}
}
然后 其他方法就可以直接调用此方法了,spring会开启多线程异步调用。
注意:关于注解失效需要注意以下几点
1,注解的方法必须是public方法
2,方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。
3,异步方法使用注解@Async的返回值只能为void或者Future
@Autowired
private Test test;
@Override
public List<Integer> findByMonth() {
List<Integer> list = new ArrayList<>();
for (int i =1;i<=12;i++){
int count= baseMapper.findByMonth(i);
list.add(count);
}
System.out.println("开始执行多线程任务1111111111:::"+System.currentTimeMillis());
for (int i =0;i<=5;i++){
test.doTaskThree(i);
}
System.out.println("主线程继续执行222222222222222:::::"+Thread.currentThread().getName());
return list;
}
然后用postman调用发现了一个问题如下,主线程根本没有等待子线程运行完就直接往下执行并且返回,
开始执行多线程任务1111111111:::1576198126421
主线程继续执行222222222222222:::::http-nio-8300-exec-1
第001完成任务,耗时:10000毫秒,线成名为::tsak-asyn2
第002完成任务,耗时:10000毫秒,线成名为::tsak-asyn3
第005完成任务,耗时:10000毫秒,线成名为::tsak-asyn6
第000完成任务,耗时:10000毫秒,线成名为::tsak-asyn1
第004完成任务,耗时:10000毫秒,线成名为::tsak-asyn5
第003完成任务,耗时:10000毫秒,线成名为::tsak-asyn4
这种情况肯定不是我们想要的,那么我们怎么才能让主线程等待子线程呢?不知道大家有没有了解过多线程里面的CountDownLatch、Semaphone,CyclicBarrier,们怎么在这里我们就可以用CountDownLatch或者CyclicBarrier来解决我们现在遇到的问题 关于他们的了解后续我会写一点自己的,如果大家有需要可以到网上去找找相关博客学习CountDownLatch、Semaphone学习
@Autowired
private Test test;
@Override
public List<Integer> findByMonth() {
List<Integer> list = new ArrayList<>();
for (int i =1;i<=12;i++){
int count= baseMapper.findByMonth(i);
list.add(count);
}
CountDownLatch countDownLatch = new CountDownLatch(6);
System.out.println("开始执行多线程任务1111111111:::"+System.currentTimeMillis());
for (int i =0;i<=5;i++){
test.doTaskThree(countDownLatch,i);
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程继续执行222222222222222:::::"+Thread.currentThread().getName());
return list;
@Component
public class Test {
//注意这个多线程方法的类一定要加@Component注解,拿给spring容器管理
@Async
public void doTaskThree(CountDownLatch countDownLatch,int i) {
long start = System.currentTimeMillis();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("第00" + i + "完成任务,耗时:" + (end - start) + "毫秒,线成名为::" + Thread.currentThread().getName());
countDownLatch.countDown();
}
}
修改之后我们再来看看结果呢,这下我们看到结果按照我们想要的来执行了
- 开始执行多线程任务1111111111:::1576198763725
- 第005完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-6
- 第004完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-5
- 第000完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-1
- 第003完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-4
- 第002完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-3
- 第001完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-2
- 主线程继续执行222222222222222:::::http-nio-8300-exec-1
但是我们连续调用还是发现了一个问题,那就是每次都是重新开启线成,大家知道多线程的创建和销毁是非常消耗cpu资源的,所以怎么解决呢?那就是使用spring的线程池,然后给@Async属性赋值@Async("getTaskExector"),表示使用此线程池。
@Bean("getTaskExector")
public Executor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int threadCount = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(threadCount);//核心池大小
executor.setMaxPoolSize(threadCount);//最大线程数
executor.setQueueCapacity(1000);//队列程度
executor.setKeepAliveSeconds(1000);//线程空闲时间
executor.setThreadNamePrefix("tsak-asyn");//线程前缀名称
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//配置拒绝策略
return executor;
}
经过多次调用发现使用了线程池里面的线成重复使用了
开始执行多线程任务1111111111:::1576200103134
第002完成任务,耗时:10000毫秒,线成名为::tsak-asyn3
第001完成任务,耗时:10000毫秒,线成名为::tsak-asyn2
第003完成任务,耗时:10000毫秒,线成名为::tsak-asyn4
第005完成任务,耗时:10000毫秒,线成名为::tsak-asyn6
第004完成任务,耗时:10000毫秒,线成名为::tsak-asyn5
第000完成任务,耗时:10000毫秒,线成名为::tsak-asyn1
主线程继续执行222222222222222:::::http-nio-8300-exec-3
开始执行多线程任务1111111111:::1576200126619
第003完成任务,耗时:10000毫秒,线成名为::tsak-asyn2
第004完成任务,耗时:10000毫秒,线成名为::tsak-asyn4
第001完成任务,耗时:10000毫秒,线成名为::tsak-asyn8
第000完成任务,耗时:10000毫秒,线成名为::tsak-asyn7
第005完成任务,耗时:10000毫秒,线成名为::tsak-asyn6
第002完成任务,耗时:10000毫秒,线成名为::tsak-asyn3
主线程继续执行222222222222222:::::http-nio-8300-exec-2
开始执行多线程任务1111111111:::1576200167044
第005完成任务,耗时:10001毫秒,线成名为::tsak-asyn7
第003完成任务,耗时:10001毫秒,线成名为::tsak-asyn4
第004完成任务,耗时:10001毫秒,线成名为::tsak-asyn8
第000完成任务,耗时:10001毫秒,线成名为::tsak-asyn5
第002完成任务,耗时:10001毫秒,线成名为::tsak-asyn2
第001完成任务,耗时:10001毫秒,线成名为::tsak-asyn1
主线程继续执行222222222222222:::::http-nio-8300-exec-1
还有一种就是使用多线程中带返回的 Future结果来进行主线程的控制,大概如下,可供参考。
@Async("getTaskExector")
public Future<String> doTaskThree(int i) {
long start = System.currentTimeMillis();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("第00" + i + "完成任务,耗时:" + (end - start) + "毫秒,线成名为::" + Thread.currentThread().getName());
return new AsyncResult("SUCUESS");
}
System.out.println("开始执行多线程任务1111111111:::"+System.currentTimeMillis());
List<Future<String>> list1 = new ArrayList<>();
for (int i =0;i<=5;i++){
Future<String> stringFuture = test.doTaskThree(i);
list1.add(stringFuture);
}
boolean flag = false;
while (!flag){
for (Future<String> future:list1){
try {
String s = future.get();
if (s =="成功"){
flag = true;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
System.out.println("主线程继续执行222222222222222:::::"+Thread.currentThread().getName());
总结:
方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的.