简介
一般业务处理放置在service层处理的数据库操作,我们都会使用spring事务进行管理,可是当我们使用多线程时候,线程脱离spring管理,因此是无法进行事务管理的,只能手动管理事务。
该Demo演示如何手动管理多线程事务。
码云Demo地址:https://gitee.com/zhaojiyuan/thread-transactional-demo
关键代码
添加任务
/**
* 添加要异步执行的方法程序
*
* @param supplier 任务
*/
public void addFunction(Supplier supplier) {
supplierList.add(supplier);
}
执行队列中的任务
/**
* 执行队列中的任务
*/
public Boolean execute() {
if (supplierList.size() > THREAD_COUNT_WARN) {
log.warn("当前线程数为:{}个,线程数大于数据库连接数将发生死锁");
}
// 创建一个线程池
executorService = Executors.newFixedThreadPool(supplierList.size());
// 初始化 线程 计数器
countDownLatch = new CountDownLatch(supplierList.size());
// 提交任务
supplierList.forEach(it -> executorService.submit(new TransactionRunnable(platformTransactionManager, it)));
try {
// 主线程 等待计数器为 0 时 进行提交或回滚
countDownLatch.await();
if (isError.get()) {
log.error("多线程执行失败");
return false;
}
} catch (InterruptedException e) {
// 打印异常日志
log.error("多线程执行失败" + e.getMessage());
return false;
}
return true;
}
多线程任务执行
/**
* 实现 Runnable 接口
*/
class TransactionRunnable implements Runnable {
/**
* 事务管理器
*/
private PlatformTransactionManager platformTransactionManager;
/**
* 当前任务
*/
private Supplier supplier;
/**
* 构造函数
*
* @param platformTransactionManager 事务管理器
* @param supplier 当前任务
*/
public TransactionRunnable(PlatformTransactionManager platformTransactionManager, Supplier supplier) {
this.platformTransactionManager = platformTransactionManager;
this.supplier = supplier;
}
@Override
public void run() {
log.debug("子线程执行:开始");
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 创建当前线任务的事务
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus transaction = this.platformTransactionManager.getTransaction(def);
log.debug("子线程执行:开始");
try {
// 尝试获取任务值
supplier.get();
} catch (Exception e) {
// 异常时,设置错误标记
isError.set(true);
log.error("子线程执行:异常{}", e.getMessage());
}
// 线程计数器 -1
countDownLatch.countDown();
try {
// 子线程 等待计数器为 0 时 进行提交或回滚
countDownLatch.await();
if (isError.get()) {
//事务回滚
platformTransactionManager.rollback(transaction);
log.info("子线程执行:事务回滚");
} else {
//事务提交
platformTransactionManager.commit(transaction);
log.info("子线程执行:事务提交");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
调用示例
// 定义此主线程的 异步处理时事务管理
MultiThreadTransactionComponent mt = new MultiThreadTransactionComponent(dataSourceTransactionManager);
// 创建10个任务,插入到数据库中
IntStream.range(0, 10).forEach(it -> {
mt.addFunction(() -> {
testMapper.insertTestData(it + "");
// 模拟发生异常
if (it == 9) {
int i = 1 / 0;
}
return 0;
});
});
// 执行任务
mt.execute();
测试
通过http请求调用controller进行测试:
http://localhost:8801/springboot/base/test/threadTransactionalTest
注意事项
- 因为事务需要所有线程都执行完成之后一块提交,因此无法使用线程池管理线程,因为当任务数量高于线程池核心任务数后,没有提交事务的线程会一直占用线程,所以剩余未执行的任务永远等待可用线程造成死锁。当任务数量小于核心任务数时候,也没有必要使用线程池。
- 请确认任务数量一定要小于项目数据库连接池数量,要不然未提交的事务会一直占用数据库连接,新的任务无法获取到数据库连接,造成死锁。
- 综合以上原因,建议尽量少用、慎用多线程处理数据库操作。我也在学习中,欢迎交流,共同学习。
- 最新代码以码云为准
致谢
参考【阳宗德】的博客:手动事务及多线程事务
参考地址:https://blog.csdn.net/qq_35385687/article/details/116978028