总而言之其实就是线程异步问题,由于
@Async
封装之后导致大家都会忘记去处理这些问题
1. 事务失效问题
- 场景一(事务生效), 同一service类下2个方法, 因为此时调用的时候@Async没有生效,还是在调用的线程中执行的,没有异步
@Service
public class AsyncTxTestService {
@Autowired
AsyncTxTestMapper baseMapper;
@Transactional(rollbackFor = Throwable.class)
public void t1() {
log.info("t1=====");
AsyncTxTest asyncTxTest = new AsyncTxTest();
asyncTxTest.setCommCode(UUID.randomUUID().toString().substring(0,20));
asyncTxTest.setVisitId(123L);
asyncTxTest.setReferenceCode("123L");
baseMapper.insert(asyncTxTest);
t3();
}
@Async
public void t3(){
log.info("t3=====");
AsyncTxTest asyncTxTest = new AsyncTxTest();
asyncTxTest.setCommCode(UUID.randomUUID().toString().substring(0,20));
asyncTxTest.setVisitId(789L);
asyncTxTest.setReferenceCode("789L");
baseMapper.insert(asyncTxTest);
System.out.println(Thread.currentThread().getName());
throw new RuntimeException("789");
}
}
- 场景二(事务失效), 不同service类下2个方法, 此场景下 出现异常 两个保存都会保存成功
@Async 方法不添加 @Transactional
@Service
public class AsyncTxTestService {
@Autowired
AsyncTxTestMapper baseMapper;
@Autowired
AsyncTxTestServiceTwo asyncTxTestServiceTwo
@Transactional(rollbackFor = Throwable.class)
public void t1() {
log.info("t1=====");
asyncTxTestServiceTwo.t2();
asyncTxTest asyncTxTest = new asyncTxTest();
asyncTxTest.setCommCode(UUID.randomUUID().toString().substring(0,20));
asyncTxTest.setVisitId(456L);
asyncTxTest.setReferenceCode("456L");
baseMapper.insert(asyncTxTest);
}
}
@Service
public class AsyncTxTestServiceTwo {
@Autowired
protected AsyncTxTestMapper baseMapper;
@Async
public void t2(){
log.info("t2=====");
asyncTxTest asyncTxTest = new asyncTxTest();
asyncTxTest.setCommCode(UUID.randomUUID().toString().substring(0,20));
asyncTxTest.setVisitId(456L);
asyncTxTest.setReferenceCode("456L");
baseMapper.insert(asyncTxTest);
System.out.println(Thread.currentThread().getName());
throw new RuntimeException("456");
}
}
- 场景三(事务生效 - 主方法t1和异步方法t3分属2个事务 互不干涉), 不同service类下2个方法,@Async 方法添加 @Transactional
@Service
public class AsyncTxTestService {
@Autowired
AsyncTxTestMapper baseMapper;
@Autowired
AsyncTxTestServiceTwo asyncTxTestServiceTwo
@Transactional(rollbackFor = Throwable.class)
public void t1() {
log.info("t1=====");
asyncTxTestServiceTwo.t2();
asyncTxTest asyncTxTest = new asyncTxTest();
asyncTxTest.setCommCode(UUID.randomUUID().toString().substring(0,20));
asyncTxTest.setVisitId(456L);
asyncTxTest.setReferenceCode("456L");
baseMapper.insert(asyncTxTest);
}
}
@Service
public class AsyncTxTestServiceTwo {
@Autowired
protected AsyncTxTestMapper baseMapper;
@Async
@Transactional(rollbackFor = Throwable.class)
public void t2(){
log.info("t2=====");
asyncTxTest asyncTxTest = new asyncTxTest();
asyncTxTest.setCommCode(UUID.randomUUID().toString().substring(0,20));
asyncTxTest.setVisitId(456L);
asyncTxTest.setReferenceCode("456L");
baseMapper.insert(asyncTxTest);
System.out.println(Thread.currentThread().getName());
throw new RuntimeException("456");
}
}
根据场景一二三可以看出
- 同一service方法互相调用@Async不生效,不是异步所以事务可以生效
- 对应的异步方法支持事务,但是调用的线程是无法捕获@Async方法的异常的,所以调用的线程无法回滚
如果想捕获到异步方法的异常可以使用CompletableFuture类作为返回结果,get获取
@Service
public class AsyncTxTestService {
@Autowired
AsyncTxTestMapper baseMapper;
@Autowired
AsyncTxTestServiceTwo asyncTxTestServiceTwo
@Transactional(rollbackFor = Throwable.class)
public void t1() throws ExecutionException, InterruptedException {
log.info("t1=====");
CompletableFuture<Boolean> future = asyncTxTestServiceTwo.t2();
CompletableFuture<Boolean> future2 = asyncTxTestServiceTwo.t2();
CompletableFuture<Boolean> future3 = asyncTxTestServiceTwo.t2();
CompletableFuture<Boolean> future4 = asyncTxTestServiceTwo.t2();
asyncTxTest asyncTxTest = new asyncTxTest();
asyncTxTest.setCommCode(UUID.randomUUID().toString().substring(0,20));
asyncTxTest.setVisitId(456L);
asyncTxTest.setReferenceCode("456L");
baseMapper.insert(asyncTxTest);
// 等待异步执行完成
CompletableFuture.allOf(future, future2,future3,future4).get();
}
}
@Service
public class AsyncTxTestServiceTwo {
@Autowired
protected AsyncTxTestMapper baseMapper;
@Async
@Transactional(rollbackFor = Throwable.class)
public CompletableFuture<Boolean> t2(){
log.info("t2=====");
asyncTxTest asyncTxTest = new asyncTxTest();
asyncTxTest.setCommCode(UUID.randomUUID().toString().substring(0,20));
asyncTxTest.setVisitId(456L);
asyncTxTest.setReferenceCode("456L");
baseMapper.insert(asyncTxTest);
if (true) {
throw new RuntimeException("456");
}
return CompletableFuture.completedFuture(Boolean.TRUE);
}
}
2. RequestContextHolder上下文丢失
// 此时 如果这个逻辑是在@Async中调用的话,就获取不到request中的信息
@Async
public void completeToDoList(Integer tid, Long id, Integer state) {
// 此时即可获取到
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
}
如果要支持子线程也能获取到信息的话,需要自定义线程池修饰类,将主线程的请求信息放到子线程中一份:
也可以使用TTL
@Bean(name = "asyncServiceExecutor")
public Executor asyncServiceExecutor() {
log.info("start asyncServiceExecutor----------------");
//使用可视化运行状态的线程池
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(5);
//配置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(20);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("async-");
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//增加线程池修饰类
executor.setTaskDecorator(new CustomTaskDecorator());
//执行初始化
executor.initialize();
log.info("end asyncServiceExecutor------------");
return executor;
}
public class CustomTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 获取主线程中的请求信息(我们的用户信息也放在里面)
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return () -> {
try {
// 将主线程的请求信息,设置到子线程中
RequestContextHolder.setRequestAttributes(attributes);
// 执行子线程,这一步不要忘了
runnable.run();
} finally {
// 线程结束,清空这些信息,否则可能造成内存泄漏
RequestContextHolder.resetRequestAttributes();
}
};
}
}
// 指定线程池
@Async("asyncServiceExecutor")
public void completeToDoList(Integer tid, Long id, Integer state) {
// 此时即可获取到
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
}