@Async异步使用需要注意的问题

总而言之其实就是线程异步问题,由于@Async封装之后导致大家都会忘记去处理这些问题

1. 事务失效问题
  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");
    }
}



  1. 场景二(事务失效), 不同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");
    }

}

  1. 场景三(事务生效 - 主方法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");
    }

}

根据场景一二三可以看出

  1. 同一service方法互相调用@Async不生效,不是异步所以事务可以生效
  2. 对应的异步方法支持事务,但是调用的线程是无法捕获@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();
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生如夏花般绚丽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值