SpringBoot异步多线程调用注解@Async使用和CountDown

 关于多线成调用可能大家用的比较多的是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(); 
    } 
  
} 

修改之后我们再来看看结果呢,这下我们看到结果按照我们想要的来执行了

 

 
  1. 开始执行多线程任务1111111111:::1576198763725 
  2. 第005完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-6 
  3. 第004完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-5 
  4. 第000完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-1 
  5. 第003完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-4 
  6. 第002完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-3 
  7. 第001完成任务,耗时:10000毫秒,线成名为::SimpleAsyncTaskExecutor-2 
  8. 主线程继续执行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()); 
  
总结:方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的.
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值