基于SpringBoot+Async注解整合多线程

提示:本文没有使用原生的创建线程方式,默认已掌握创建线程的四种方式
全文基于SpringBoot框架,要求读者掌握SpringBoot操作
本人能力有限,如有遗漏或错误,敬请指正,谢谢

文章目录

  • 其他文章
  • 前言
  • 一、为什么要使用多线程?
  • 二、业务中使用多线程方式
    • 1.传统方式
    • 2.使用Async注解
    • 3.源码分析:@Async使用的默认线程池
    • 4.不使用默认线程池,自定义线程池
    • 5.案例:批量插入数据
  • 总结


其他文章

1.Java多线程基本概念和常用API(面试高频)
2.线程安全问题(synchronized解决,各种类型全)
3.Java并发线程池使用和原理(通俗易懂版)
4.基于SpringBoot+Async注解整合多线程

前言

学习一门技术最好使用wwh方法
what:这门技术是什么
why:为什么用这个技术,使用会有什么优化
how:怎么使用


提示:以下是本篇文章正文内容,下面案例可供参考

一、为什么要使用多线程?

先谈谈单线程的使用:
在普通业务下,要查询多个表,调用多个接口(比如根据id查询A表,B表,C表),同步进行,要等待每个接口执行后才走下一个接口,耗时久
基于SpringBoot+Async注解整合多线程_第1张图片
为了提高效率,使用多线程异步的方式:
基于SpringBoot+Async注解整合多线程_第2张图片

二、业务中使用多线程方式

1.传统方式

代码如下(示例):手动开启多线程,如果业务都需要,那么每个都要自己编码

public static void main(String[] args) {
    System.out.println("主线程开启");
    //创建线程池
    ThreadPoolExecutor executor=new ThreadPoolExecutor(5,5,10, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
    executor.execute(()->{
        try {
            Thread.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"多线程执行");
    });
    System.out.println("主线程结束");
}

2.使用Async注解

  • 配置类加上@EnableAsync() :开启异步执行
  • 在调用的方法上加上@Async:
    可以让某个方法变成异步调用的时候,不是以主线程调用,而是取线程池里的线程调用,多线程执行
  • @Async(“指定线程池名”) 不指定用默认线程池

@Async生效条件:
1.方法是public的
2.在同一个类中调用无效,因为它绕过代理并直接调用底层方法。如A和B方法在一个类中,在B方法上加@Async注解,A调用B是不会使用多线程的

代码如下(示例):
在多线程方法中睡眠3s更方便体现异步执行

@RequestMapping("/test")
public void test(){
    System.out.println("主线程"+Thread.currentThread().getName()+"开启");
    threadTask.test();
    System.out.println("主线程结束");
}

@Async()
void test() {
    System.out.println(Thread.currentThread().getName()+"被调用了");
    try {
        Thread.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+"结束了");
}
输出:
主线程 xxx 开启
主线程结束
异步线程名 被调用
异步线程名 结束了

3.源码分析:@Async使用的默认线程池

默认使用Spring创建ThreadPoolTaskExecutor。

默认核心线程数:8,最大线程数:Integet.MAX_VALUE
队列使用LinkedBlockingQueue,容量是:Integet.MAX_VALUE
空闲线程保留时间:60s
线程池拒绝策略:AbortPolicy

源码分析:
线程名以task-开头
基于SpringBoot+Async注解整合多线程_第3张图片
默认线程池配置
基于SpringBoot+Async注解整合多线程_第4张图片

4.不使用默认线程池,自定义线程池

ThreadPoolTaskExecutor是spring core包中的,而ThreadPoolExecutor是JDK中的JUC。ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理。
Spring项目用ThreadPoolTaskExecutor更方便

@Configuration
@EnableAsync //启用异步任务,开启多线程    配合@Async注解在方法上,表示这个方法被调用时,会从线程池拿出线程来调用
public class ThreadConfig {
    @Bean
    public ThreadPoolTaskExecutor executor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
      	//配置核心线程数
        executor.setCorePoolSize(15);
      	//配置最大线程数
        executor.setMaxPoolSize(30);
      	//配置队列大小
        executor.setQueueCapacity(1000);
      	//线程的名称前缀
        executor.setThreadNamePrefix("Executor-");
      	//线程活跃时间(秒)
        //executor.setKeepAliveSeconds(60);
      	//等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
      	//设置拒绝策略
        //executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
      	//执行初始化
        executor.initialize();
        return executor;
    }
}

5.案例:批量插入数据

插入100w数据,分10次插入,采用多线程方式
持久层使用mybatis-plus
使用JUC线程类

@RestController
public class TestController {
    @Autowired
    private ThreadPoolTaskExecutor executor;
    @Autowired
    private UserService userService;

   @RequestMapping("/test1")
    public void test1(){
    	//10表示线程有10个 
       CountDownLatch cdl = new CountDownLatch(10); 
       Long start=System.currentTimeMillis();
       for(int i=1;i<=10;i++){
           executor.execute(() -> {
               System.out.println("开启线程:"+Thread.currentThread().getName());
               List<User> list=new ArrayList<>();
               for(int j=1;j<=100000;j++){
                   list.add(new User(UUID.randomUUID().toString(),"aaa"));
               }
               userService.saveBatch(list,10000);
               //每执行完一个线程,就减一
               cdl.countDown(); 
           });

       }
       try {
       //如果线程数不为0,就一直等待,不用这个判断算不出 本次插入时间
           cdl.await(); 
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       Long end=System.currentTimeMillis();
       System.out.println(end-start);
    }
}

总结

使用步骤:
1.不想用Spring配置的线程池,则自己创建Bean
2.开启@EnableAsync异步
3.在调用的方法加上@Async注解

你可能感兴趣的:(Java中的多线程,spring,boot,java,spring,后端)