CountDownLatch 是java并发包下的一个计数器。通过每个线程执行完毕计数器减1和子线程共享的变量实现子线程间的通信控制。
- 单次请求时,含有过多的对数据库表的写操作与数据处理的逻辑,并且此处可以根据不同的表将这些操作拆分开来。为了更快的提高处理速度,此处想到了使用多线程去处理这些拆开的操作。
- 首先需要两个核心共享变量,其一是CountDownLatch,另一个则是AtomicBoolean 。这两个都是JUC包下的工具类,是线程安全的。
- CountDownLatch可以理解为一个共享的扣减的计数器,核心是JUC包下的AQS。初始化时需要指定计数器的值且不能小于等于0,当子线程执行完任务后调用countDownLatch.countDown() 方法则会将计数器的值扣去1,接着调用await() 方法将当前线程进行阻塞,直到计数器值为0且所有线程调用了await() 方法,将唤醒线程。
- 子线程执行的过程中出现异常,则将AtomicBoolean 的值置为false,当所有线程苏醒后会对这个共享变量的值进行判断,false则报错,通过注解@Transactional将子线程的事务进行回滚。子线程执行完毕后,父线程收到子线程的异常也进行回滚与异常处理,将整体的大事务拆成了父线程的事务与多个子线程的事务。
- 多线程的实现采用了 CompletableFuture 与 Spring的线程池。
- 父线程的逻辑代码:
@Transactional(rollbackFor = Exception.class)
public Boolean saveNurseOrganizationStudio(NurseOrganizationStudioPcDetailVo pcDetailVo) {
// ===义务逻辑代码省略===
// 异步处理 子线程任务是否执行成功的标识
AtomicBoolean successFlag = new AtomicBoolean(Boolean.TRUE);
// 线程通信的计数器
CountDownLatch countDownLatch = new CountDownLatch(2);
// 工作室成员 保存处理逻辑
CompletableFuture<Void> membersCompletableFuture = threadUtil.runAsync(() -> nurseOrganizationStudioMemberService.membersHandler(saveFlag, pcDetailVo, countDownLatch, successFlag));
// 工作室服务 保存处理逻辑
CompletableFuture<Void> servicesCompletableFuture = threadUtil.runAsync(() -> nurseOrganizationStudioServicesService.servicesHandler(saveFlag, pcDetailVo, countDownLatch, successFlag));
try {
CompletableFuture.allOf(membersCompletableFuture, servicesCompletableFuture).get();
} catch (Exception e) {
log.error(e.getMessage());
throw new BusinessException(e.getMessage());
}
return Boolean.TRUE;
}
- 对 CompletableFuture 的简单封装
// 线程池
@Autowired
private TaskExecutor taskExecutor;
/**
* 注入 Runnable
* 获取基本的的 CompletableFuture
*/
public CompletableFuture<Void> runAsync(Runnable runnable) {
return CompletableFuture.runAsync(runnable, taskExecutor);
}
- runnable代码,调用了childThreadTransactional(Runnable runnable, CountDownLatch countDownLatch, AtomicBoolean successFlag)方法
/**
* 工作室成员 保存处理逻辑
* @param countDownLatch 线程通信的计数器
* @param successFlag 线程事务的成功与否标识
**/
@Transactional(rollbackFor = Exception.class)
public void membersHandler(CountDownLatch countDownLatch, AtomicBoolean successFlag) {
threadUtil.childThreadTransactional(() -> {
// 业务代码
}, countDownLatch, successFlag);
}
- 抽出来的子线程的核心 通用代码。核心想法是将业务逻辑代码作为一个 runnable放在该方法里调用。
/**
* 多个子线程都有事务需要进行处理时使用,属性countDownLatch 与 属性successFlag需要在父线程创建,子线程调用的对象也需要加 事务注解通过抛异常让子线程进行回滚
* 逻辑:
* 1、执行代码块,异常则抛出并将事务状态改为false,此时调用方的 事务注解配的事务管理器将进行回滚
* 2、代码块执行结束,将多线程的计数器计数 减1
* 3、调用 countDownLatch.await,当前线程进入等待状态,当计数器为0时唤醒返回值为True,超时为False
* 4、超时或者其他子线程异常 则抛出异常进行回滚
*
* @param runnable 要执行的代码块
* @param countDownLatch 计数器
* @param successFlag 子线程间共享的 任务异常与否的标识
**/
public void childThreadTransactional(Runnable runnable, CountDownLatch countDownLatch, AtomicBoolean successFlag) {
try {
runnable.run();
} catch (Exception e) {
successFlag.set(Boolean.FALSE);
log.error(e.getMessage());
commonUtil.throwBizException(e.getMessage());
} finally {
// 线程执行完毕计数器减1
countDownLatch.countDown();
try {
// 阻塞线程,当所有任务执行完 countDownLatch.countDown() 代码后唤醒当前线程;超时返回false
boolean await = countDownLatch.await(3L, TimeUnit.SECONDS);
// 超时或者 其他子线程异常 将状态置为 false 也需要抛出异常
Assert.isTrue(!await || !successFlag.get(), CommonEnum.ExceptionDesc.服务异常);
} catch (InterruptedException e) {
successFlag.set(Boolean.FALSE);
log.error(e.getMessage());
commonUtil.throwBizException(e.getMessage());
}
}
}
- 代码已上线,亲测可用。
- 文末,该方法实现了但是过于繁琐,在后期我对其想进行二次封装,思路代码如下。大体思路是将 CountDownLatch 与 AtomicBoolean 的初始化抽出,将各个任务试做一个个 的Runnable 对象,并发的去执行。项目架构上 ThreadUtil是在base包中,没有 spring的依赖,因此无法对事务进行控制,该方案未亲测。原理同上,就是在 this.childThreadTransactional 方法调用时由该方法 加上事务注解 控制子线程的事务的回滚。
/**
* todo runnable.run() 是单独的事务,其他线程报错,通过异常控制正确的线程进行回滚但是事务已经执行完毕且提交了,不在事务管理器控制范围内,要使用此方法需要将该工具类提到外层在 childThreadTransactional() 方法上 加事务注解,由此处控制事务方可实现
*/
/**
* 多个需要事务管控的任务 异步处理 通过 CountDownLatch 保证异步事务的正确管理
* 每个 Runnable 需要有独立的 @Transactional 注解,通过报错将单个任务回滚,通过 AtomicBoolean 的共享变量将异常告知其他线程 并报错进行回滚
*
* @param runnables
* @return void
* @Author chenkaimin
* @date 10:43 2022/6/9
**/
public void multiTransactional(Runnable... runnables) {
if (runnables.length == 0) {
return;
}
CountDownLatch countDownLatch = new CountDownLatch(runnables.length);
AtomicBoolean successFlag = new AtomicBoolean(Boolean.TRUE);
List<CompletableFuture<Void>> completableFutures = Stream.of(runnables).map(runnable -> {
// 异步
CompletableFuture<Void> completableFuture = this.runAsync(() -> {
// 执行需要 事务管理的 任务
this.childThreadTransactional(() -> runnable.run(), countDownLatch, successFlag);
});
return completableFuture;
}).collect(Collectors.toList());
int size = completableFutures.size();
try {
// 执行
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[size])).get();
} catch (Exception e) {
log.error(e.getMessage());
commonUtil.throwBizException(e.getMessage());
}
}