SpringBoot多线程@Async使用体验

场景

  1. 导入:可以将大批量的数据insert操作采用多线程的方式并行执行
  2. 第三方服务的接口调用:由于存在个别第三方服务调用比较耗时的场景,此时就可以与自身服务的逻辑并行执行

简而言之:接口中部份业务逻辑可以通过并行的方式来优化接口性能

1.线程池配置

@Configuration
@EnableAsync
public class TaskPoolConfig {

    @Bean("taskExecutor") // bean 的名称,默认为首字母小写的方法名
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程数(CPU核心数+1)
        executor.setCorePoolSize(10);
        //最大线程数(2*CPU核心数+1)
        executor.setMaxPoolSize(20);
        //缓冲队列数
        executor.setQueueCapacity(200);
        //允许线程空闲时间(单位:默认为秒)
        executor.setKeepAliveSeconds(60);
        //线程池名前缀
        executor.setThreadNamePrefix("sub-thread-");
        // 增加 TaskDecorator 属性的配置
        executor.setTaskDecorator(new ContextDecorator());
        // 线程池对拒绝任务的处理策略:CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

2.子父线程之间共享一个Request的配置方案

1.实现TaskDecorator接口

/**
 * 子线程装饰器
 *
 * @author Da Shuai
 * @date 2021-06-10 18:28:17
 */
public class SubThreadTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        RequestAttributes context = RequestContextHolder.currentRequestAttributes();
        return () -> {
            try {
                RequestContextHolder.setRequestAttributes(context);
                runnable.run();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

2.之前的线程池配置加如下代码使其生效

// 增加 TaskDecorator 属性的配置
executor.setTaskDecorator(new ContextDecorator());

3.阻塞主线程,等待所有子线程执行完毕后继续执行主线程

1.CountDownLatch

思路:

  1. 实例化CountDownLatch对象,同时传入x(线程数量:这个数量必须等于子线程数量)进行构造
  2. 每个子线程执行完毕后会调用countDown()方法
  3. 子线程逻辑后方调用await()方法

这样线程计数器为0之前,主线程就一直处于pending状态

主线程逻辑
new CountDownLatch(X)
latch.await()

@Override
@Transactional
public void importExcel(File file) {
    CountDownLatch latch = new CountDownLatch(3);
    for (int i = 0; i < 3; i++) {
        VoteDO voteDO = new VoteDO();
        voteDO.setTitle(i + "");
        asyncManager.asyncSaveVote(voteDO);
    }
    //System.out.println(1/0);
    try {
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

子线程逻辑
latch.countDown()

@Override
@Async
public void asyncSaveVote(VoteDO voteDO, CountDownLatch latch) {
    log.info("当前线程为 {},休眠10s开始", Thread.currentThread().getName());
    try {
        Thread.sleep(10000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("当前线程为 {},休眠10s结束", Thread.currentThread().getName());
    log.info("当前线程为 {},保存开始", Thread.currentThread().getName());
    voteDO.setDesc(Thread.currentThread().getName());
    voteDao.insert(voteDO);
    latch.countDown();
    log.info("当前线程为 {},保存结束", Thread.currentThread().getName());
}

日志

2021-06-11 16:31:08.653  INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect                : ===============请求内容===============
2021-06-11 16:31:08.653  INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect                : 请求地址:http://localhost:8080/api/import
2021-06-11 16:31:08.653  INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect                : 请求方式:POST
2021-06-11 16:31:08.655  INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect                : 请求类方法:com.zhdj.controller.ImportController.importExcel
2021-06-11 16:31:08.655  INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect                : 请求类方法参数:[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@42c3f403]
2021-06-11 16:31:08.655  INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect                : ===============请求内容===============
2021-06-11 16:31:08.676  INFO 27516 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-06-11 16:31:08.894  INFO 27516 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-06-11 16:31:08.921  INFO 27516 --- [   sub-thread-3] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-3,休眠10s开始
2021-06-11 16:31:08.921  INFO 27516 --- [   sub-thread-1] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-1,休眠10s开始
2021-06-11 16:31:08.921  INFO 27516 --- [   sub-thread-2] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-2,休眠10s开始
2021-06-11 16:31:18.921  INFO 27516 --- [   sub-thread-2] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-2,休眠10s结束
2021-06-11 16:31:18.921  INFO 27516 --- [   sub-thread-3] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-3,休眠10s结束
2021-06-11 16:31:18.921  INFO 27516 --- [   sub-thread-2] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-2,保存开始
2021-06-11 16:31:18.921  INFO 27516 --- [   sub-thread-1] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-1,休眠10s结束
2021-06-11 16:31:18.921  INFO 27516 --- [   sub-thread-3] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-3,保存开始
2021-06-11 16:31:18.921  INFO 27516 --- [   sub-thread-1] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-1,保存开始
2021-06-11 16:31:19.080 DEBUG 27516 --- [   sub-thread-3] com.zhdj.dao.VoteDao.insert              : ==>  Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:31:19.080 DEBUG 27516 --- [   sub-thread-1] com.zhdj.dao.VoteDao.insert              : ==>  Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:31:19.080 DEBUG 27516 --- [   sub-thread-2] com.zhdj.dao.VoteDao.insert              : ==>  Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:31:19.156 DEBUG 27516 --- [   sub-thread-1] com.zhdj.dao.VoteDao.insert              : ==> Parameters: 0(String), sub-thread-1(String), 2021-06-11T16:31:19.032(LocalDateTime), 2021-06-11T16:31:19.037(LocalDateTime)
2021-06-11 16:31:19.156 DEBUG 27516 --- [   sub-thread-3] com.zhdj.dao.VoteDao.insert              : ==> Parameters: 2(String), sub-thread-3(String), 2021-06-11T16:31:19.032(LocalDateTime), 2021-06-11T16:31:19.037(LocalDateTime)
2021-06-11 16:31:19.156 DEBUG 27516 --- [   sub-thread-2] com.zhdj.dao.VoteDao.insert              : ==> Parameters: 1(String), sub-thread-2(String), 2021-06-11T16:31:19.032(LocalDateTime), 2021-06-11T16:31:19.037(LocalDateTime)
2021-06-11 16:31:19.172 DEBUG 27516 --- [   sub-thread-3] com.zhdj.dao.VoteDao.insert              : <==    Updates: 1
2021-06-11 16:31:19.178 DEBUG 27516 --- [   sub-thread-2] com.zhdj.dao.VoteDao.insert              : <==    Updates: 1
2021-06-11 16:31:19.187 DEBUG 27516 --- [   sub-thread-1] com.zhdj.dao.VoteDao.insert              : <==    Updates: 1
2021-06-11 16:31:19.224  INFO 27516 --- [   sub-thread-3] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-3,保存结束
2021-06-11 16:31:19.224  INFO 27516 --- [   sub-thread-1] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-1,保存结束
2021-06-11 16:31:19.224  INFO 27516 --- [   sub-thread-2] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-2,保存结束
2021-06-11 16:31:19.226  INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect                : --------------返回内容----------------
2021-06-11 16:31:19.328  INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect                : Response内容:null
2021-06-11 16:31:19.328  INFO 27516 --- [nio-8080-exec-1] com.zhdj.config.LogAspect                : --------------返回内容----------------

2.Future
思路:
1.子线程逻辑返回Future对象
2.主线程逻辑循环判断每个子线程返回的Future对象isDone()是否为true

主线程逻辑
循环判断future.isDone()是否为true

@Override
@Transactional
public void importExcel(File file) {
	List<Future> futureList = new ArrayList<>();
	for (int i = 0; i < 3; i++) {
	    VoteDO voteDO = new VoteDO();
	    voteDO.setTitle(i + "");
	    Future future = asyncManager.asyncSaveVote(voteDO);
	    futureList.add(future);
	}
	//检查所有子线程是否均执行完毕
	while (true) {
	    boolean isAllDone = true;
	    for (Future future : futureList) {
	        if (null == future || !future.isDone()) {
	            isAllDone = false;
	        }
	    }
	    if (isAllDone) {
	        log.info("所有子线程执行完毕");
	        break;
	    }
	}
}

子线程逻辑
返回Future对象

@Override
public Future asyncSaveVote(VoteDO voteDO) {
    log.info("当前线程为 {},休眠10s开始", Thread.currentThread().getName());
    try {
        Thread.sleep(10000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("当前线程为 {},休眠10s结束", Thread.currentThread().getName());
    log.info("当前线程为 {},保存开始", Thread.currentThread().getName());
    voteDO.setDesc(Thread.currentThread().getName());
    voteDao.insert(voteDO);
    log.info("当前线程为 {},保存结束", Thread.currentThread().getName());
    //返回需要用AsyncResult类
    return new AsyncResult<>(true);
}

日志

2021-06-11 16:42:28.974  INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect                : ===============请求内容===============
2021-06-11 16:42:28.974  INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect                : 请求地址:http://localhost:8080/api/import
2021-06-11 16:42:28.974  INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect                : 请求方式:POST
2021-06-11 16:42:28.975  INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect                : 请求类方法:com.zhdj.controller.ImportController.importExcel
2021-06-11 16:42:28.975  INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect                : 请求类方法参数:[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@7e23bacc]
2021-06-11 16:42:28.975  INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect                : ===============请求内容===============
2021-06-11 16:42:28.979  INFO 20492 --- [   sub-thread-5] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-5,休眠10s开始
2021-06-11 16:42:28.979  INFO 20492 --- [   sub-thread-4] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-4,休眠10s开始
2021-06-11 16:42:28.979  INFO 20492 --- [   sub-thread-6] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-6,休眠10s开始
2021-06-11 16:42:38.980  INFO 20492 --- [   sub-thread-6] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-6,休眠10s结束
2021-06-11 16:42:38.980  INFO 20492 --- [   sub-thread-4] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-4,休眠10s结束
2021-06-11 16:42:38.980  INFO 20492 --- [   sub-thread-5] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-5,休眠10s结束
2021-06-11 16:42:38.980  INFO 20492 --- [   sub-thread-6] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-6,保存开始
2021-06-11 16:42:38.980  INFO 20492 --- [   sub-thread-5] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-5,保存开始
2021-06-11 16:42:38.980  INFO 20492 --- [   sub-thread-4] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-4,保存开始
2021-06-11 16:42:38.981 DEBUG 20492 --- [   sub-thread-4] com.zhdj.dao.VoteDao.insert              : ==>  Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:42:38.981 DEBUG 20492 --- [   sub-thread-5] com.zhdj.dao.VoteDao.insert              : ==>  Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:42:38.981 DEBUG 20492 --- [   sub-thread-6] com.zhdj.dao.VoteDao.insert              : ==>  Preparing: INSERT INTO vote ( title, `desc`, gmt_create, gmt_modified ) VALUES ( ?, ?, ?, ? )
2021-06-11 16:42:38.982 DEBUG 20492 --- [   sub-thread-5] com.zhdj.dao.VoteDao.insert              : ==> Parameters: 1(String), sub-thread-5(String), 2021-06-11T16:42:38.980(LocalDateTime), 2021-06-11T16:42:38.981(LocalDateTime)
2021-06-11 16:42:38.982 DEBUG 20492 --- [   sub-thread-4] com.zhdj.dao.VoteDao.insert              : ==> Parameters: 0(String), sub-thread-4(String), 2021-06-11T16:42:38.980(LocalDateTime), 2021-06-11T16:42:38.981(LocalDateTime)
2021-06-11 16:42:38.982 DEBUG 20492 --- [   sub-thread-6] com.zhdj.dao.VoteDao.insert              : ==> Parameters: 2(String), sub-thread-6(String), 2021-06-11T16:42:38.980(LocalDateTime), 2021-06-11T16:42:38.981(LocalDateTime)
2021-06-11 16:42:38.988 DEBUG 20492 --- [   sub-thread-5] com.zhdj.dao.VoteDao.insert              : <==    Updates: 1
2021-06-11 16:42:38.989  INFO 20492 --- [   sub-thread-5] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-5,保存结束
2021-06-11 16:42:38.993 DEBUG 20492 --- [   sub-thread-6] com.zhdj.dao.VoteDao.insert              : <==    Updates: 1
2021-06-11 16:42:38.993  INFO 20492 --- [   sub-thread-6] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-6,保存结束
2021-06-11 16:42:39.004 DEBUG 20492 --- [   sub-thread-4] com.zhdj.dao.VoteDao.insert              : <==    Updates: 1
2021-06-11 16:42:39.005  INFO 20492 --- [   sub-thread-4] com.zhdj.AsyncManagerImpl                : 当前线程为 sub-thread-4,保存结束
2021-06-11 16:42:39.005  INFO 20492 --- [nio-8080-exec-2] com.zhdj.service.impl.VoteServiceImpl    : 所有子线程执行完毕
2021-06-11 16:42:39.005  INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect                : --------------返回内容----------------
2021-06-11 16:42:39.005  INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect                : Response内容:null
2021-06-11 16:42:39.005  INFO 20492 --- [nio-8080-exec-2] com.zhdj.config.LogAspect                : --------------返回内容----------------

4.多线程共用一个事务,暂时无解决方案,这是弊端

你可能感兴趣的:(SpringBoot)