前言
随着业务的不断深入,总有一些接口需要操作数据库中的很多表,前阵子公司的一个新增功能,就牵扯到了6、7个表的新增,而且其中还要对数据进行一些处理,测试了一下这个接口完成所有操作需要4s,这就不得不值得深思,通过网上查阅以及学习发现了个java提供的CountDownLatch工具类。实现的思想是基于一个分布式事务的解决方案2pc
一、CountDownLatch是什么?
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。
CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。
二、使用步骤
1.
代码如下(示例):
@Transactional(rollbackFor = Exception.class)
@Override
public void addDesignSpu(DesignSpuAddDTO designSpuAddDTO) {
LoginAppUser currentUser = userService.getCurrentUser();
Long tenantId = currentUser.getTenantId();
checkAddDTO(designSpuAddDTO, tenantId);
// 主线程的新增因为每个子线程都需要这个数据,所以放到了主线程里
DesignSpu designSpu = addSpu(designSpuAddDTO, currentUser, tenantId);
CountDownLatch countDownLatch = new CountDownLatch(1);
CountDownLatch mainDownLatch = new CountDownLatch(4);// 4是代表子线程的数量
// 用来记录子线程的运行状态,只要有一个失败就变为true
AtomicBoolean rollBackFlag = new AtomicBoolean(false);
// 用来存每个子线程的异常,把每个线程的自定义异常向vector的首位置插入,其余异常向末位置插入,避免线程不安全,所以使用vector代替list
Vector<BusinessException> exceptionVector = new Vector<>();
// 第一个子线程
purchaseStatusChangeThread.addSku(countDownLatch, mainDownLatch, rollBackFlag, exceptionVector, designSpuAddDTO, designSpu, currentUser, tenantId);
HashMap<String, List<DesignPlan>> hashMap = new HashMap<>();// 用来获取子线程的返回值
// 第二个子线程
purchaseStatusChangeThread.addPosition(countDownLatch, mainDownLatch, rollBackFlag, exceptionVector, hashMap, designSpuAddDTO, designSpu, currentUser, tenantId);
// 第三个子线程
purchaseStatusChangeThread.addDesignProcess(countDownLatch, mainDownLatch, rollBackFlag, exceptionVector, designSpuAddDTO, designSpu, currentUser);
// 第四个子线程
purchaseStatusChangeThread.addSewing(countDownLatch, mainDownLatch, rollBackFlag, exceptionVector, designSpuAddDTO, designSpu, currentUser, tenantId);
if (!rollBackFlag.get()) {
try {
// mainDownLatch等待,直到所有子线程执行完插入操作,但此时还没有提交事务
mainDownLatch.await();
countDownLatch.countDown();// 根据rollBackFlag状态放行子线程的await处,告知是回滚还是提交
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (CollectionUtils.isNotEmpty(exceptionVector)) {
throw exceptionVector.get(0);
}
// 以下是业务代码返回给前端的数据
jsonObject.put("designSpuId", designSpu.getId());
jsonObject.put("planList", hashMap.get("planList"));
return jsonObject;
}
2.purchaseStatusChangeThread线程类里的代码
代码太多,此处只以第二个子线程的代码为例,其余的同理:
@Async("taskExecutor")
public void addPosition(CountDownLatch countDownLatch, CountDownLatch mainDownLatch, AtomicBoolean atomicBoolean, Vector<BusinessException> exceptionVector, HashMap<String, List<DesignPlan>> hashMap, DesignSpuAddDTO designSpuAddDTO, DesignSpu designSpu, LoginAppUser currentUser, Long tenantId) {
// 如果这时有一个子线程已经出错,那当前线程不需要执行
if (atomicBoolean.get()) {
mainDownLatch.countDown();
return;
}
HashMap<String, Long> positionMap = new HashMap<>();
DefaultTransactionDefinition def = new DefaultTransactionDefinition();// 开启事务
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 设置事务隔离级别
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务代码…………
// ………………
// ………………
// 截止到这里上面都是业务
mainDownLatch.countDown();// 对countDownLatch-1
countDownLatch.await();// 如果countDownLatch不是0,线程会在此阻塞,直到countDownLatch变为0
// 如果能执行到这一步说明所有子线程都已经执行完毕判断如果atomicBoolean是true就回滚false就提交
if (atomicBoolean.get()) {
transactionManager.rollback(status);
} else {
transactionManager.commit(status);
}
} catch (Exception e) {
// 此处代表出现异常,把异常放到主线程的vector里,然后由主线程抛给前端
if (e instanceof BusinessException) {
exceptionVector.add(0, (BusinessException) e);
} else {
exceptionVector.add(CollectionUtils.isEmpty(exceptionVector) ? 0 : exceptionVector.size() - 1, new BusinessException(CommonErrorCode.UNKNOWN));
}
e.printStackTrace();
// 并把状态设置为true
atomicBoolean.set(true);
countDownLatch.countDown();
mainDownLatch.countDown();
// 回滚
transactionManager.rollback(status);
}
}
总结
此方案类似于分布式事务的实现原理2pc,主线程当做协调者,各子线程作为参与者,只用一个@Transactional注解肯定不能实现多线程的事务控制。