问题:
多线程破坏了方法事务的原子性。Spring 是使用ThreadLocal来存储Connection的,不同的线程Connection肯定不一样。所以我们加载Service 方法上的
@Transactional
是不起作用的。
思路:
每一个线程的事务是原子性的,所有的线程事务都提交了,这个操作的事务才是完成了。
代码:
- 明确子线程是执行业务的线程,主线程是控制事务的线程。
package com.xcr.thread.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.concurrent.CountDownLatch;
/**
* @author L
* @Description
* @Date 2021/8/25 14:51
*/
@Data
@Accessors(chain = true)
public class TransactionInfo {
/**
* 主线程控制器(子线程用)
*/
private CountDownLatch mainLatch;
/**
* 子线程控制器(主线程用)
*/
private CountDownLatch threadLatch;
/**
* 判断是否需要回滚
*/
private Boolean rollBack;
}
- 使用countDownLatch阻断线程执行,当子线程全部执行完
threadLatch.await();
子线程控制器将不再等待。
public static void executeThreadPool(List list){
if(CollectionUtils.isEmpty(list)){
return;
}
//通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CPU_NUM * 2,
CPU_NUM * 2 + 1,
KEEP_ALIVE_TIME,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy());
// 主线程控制器(子线程用)
CountDownLatch mainLatch = new CountDownLatch(1);
// 子线程控制器(主线程用)
CountDownLatch threadLatch = null;
// 判断是否需要回滚
Boolean rollBack = Boolean.FALSE;
TransactionInfo transactionInfo = new TransactionInfo();
transactionInfo.setMainLatch(mainLatch);
transactionInfo.setRollBack(rollBack);
long start = System.currentTimeMillis();
try {
List<List> split = Lists.partition(list, 1);
threadLatch = new CountDownLatch(split.size());
transactionInfo.setThreadLatch(threadLatch);
for (int i = 0; i < split.size(); i++) {
List threadList = split.get(i);
executor.execute(new SaveDataTask(batchService, threadList, i, transactionInfo));
}
threadLatch.await();
}catch (Exception e){
log.error("主线程异常,回滚所有数据");
rollBack = Boolean.FALSE;
}finally {
mainLatch.countDown();
long end = System.currentTimeMillis();
log.info("完成耗时:"+(end - start));
executor.shutdown();
}
}
- 当某一个子线程抛出异常,将transactionInfo中的rollBack设置为true。当主线程开关打开,发现每个子线程还有一段代码,是需要回滚事务的,所以虽然主线程这个时候也走完了,但是所有子线程的事务都回滚了。
package com.xcr.thread.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xcr.thread.dao.UserMapper;
import com.xcr.thread.entity.TransactionInfo;
import com.xcr.thread.service.SaveBatchService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.xcr.thread.entity.User;
import java.util.List;
/**
* @author L
* @Description 用于插入数据的业务实现
* @Date 2021/8/25 14:56
*/
@Slf4j
@Service("saveBatchService")
public class SaveBatchServiceImpl extends ServiceImpl<UserMapper, User> implements SaveBatchService {
@Override
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
public void batchSaveData(TransactionInfo transactionInfo, List list, Integer batch) throws Exception{
Exception ex = null;
try {
if(batch == 2){
System.out.println(batch/0);
}
this.saveBatch(list);
} catch (Exception e) {
log.error("保存失败需要回滚", e);
transactionInfo.setRollBack(Boolean.TRUE);
ex = new RuntimeException(e.getMessage());
} finally {
// 执行完成 等待主线程通知回滚
transactionInfo.getThreadLatch().countDown();
// 等待主线程通知
transactionInfo.getMainLatch().await();
if (transactionInfo.getRollBack()) {
// 出现异常 需要回滚
log.error("出现异常 需要连带回滚事务");
throw ex == null ? new RuntimeException("出现异常 需要连带回滚") : ex;
}
}
}
}
2021-09-26 19:10:10.166 INFO 10720 --- [nio-8111-exec-1] c.x.t.util.TransactionalExecutorUtil : 完成耗时:3285
2021-09-26 19:10:10.166 ERROR 10720 --- [pool-2-thread-4] c.x.t.service.impl.SaveBatchServiceImpl : 出现异常 需要连带回滚事务
2021-09-26 19:10:10.166 ERROR 10720 --- [pool-2-thread-3] c.x.t.service.impl.SaveBatchServiceImpl : 出现异常 需要连带回滚事务
2021-09-26 19:10:10.227 ERROR 10720 --- [pool-2-thread-3] com.xcr.thread.task.SaveDataTask : 第2批次插入失败。Exception:/ by zero
2021-09-26 19:10:10.271 ERROR 10720 --- [pool-2-thread-5] com.xcr.thread.task.SaveDataTask : 第4批次插入失败。Exception:出现异常 需要连带回滚
2021-09-26 19:10:10.271 ERROR 10720 --- [pool-2-thread-4] com.xcr.thread.task.SaveDataTask : 第3批次插入失败。Exception:出现异常 需要连带回滚
2021-09-26 19:10:10.279 ERROR 10720 --- [pool-2-thread-1] com.xcr.thread.task.SaveDataTask : 第0批次插入失败。Exception:出现异常 需要连带回滚
2021-09-26 19:10:10.283 ERROR 10720 --- [pool-2-thread-2] com.xcr.thread.task.SaveDataTask : 第1批次插入失败。Exception:出现异常 需要连带回滚
- 主要的代码都在上面,下面是一些其他不重要的代码。
package com.xcr.thread.task;
import com.xcr.thread.entity.TransactionInfo;
import com.xcr.thread.service.SaveBatchService;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* @author L
* @Description 批量插入数据业务执行(子线程)
* @Date 2021/6/11 18:05
*/
@Slf4j
public class SaveDataTask implements Runnable{
private SaveBatchService saveBatchService;
private Integer batch;
private List list;
private TransactionInfo transactionInfo;
public SaveDataTask(SaveBatchService baseService, List list, Integer batch, TransactionInfo transactionInfo){
this.saveBatchService = baseService;
this.batch = batch;
this.list = list;
this.transactionInfo = transactionInfo;
}
@Override
public void run() {
try {
saveBatchService.batchSaveData(transactionInfo, list, batch);
log.error("线程名称" + Thread.currentThread().getName() + "--->> 第" + this.batch + "批次插入成功");
}catch (Exception e){
log.error("第" + this.batch + "批次插入失败。Exception:" + e.getMessage());
}
}
}
总结:
其实这是分布式事务的内容,主流的分布式框架seata,下一篇会写。
小弟第一次写博客,希望大家多多指导。