Spring @Async踩到的坑

Table of Contents

背景

不要在同一个类里面调用异步方法

杜绝AopContext.currentProxy()

没有定义线程池

循环依赖问题解决


背景

Spring中用@Async注解标记的方法,称为异步方法,其实就相当于我们自己在当前方法里面:new Thread(()-> System.out.println("hello world !"))。

 按@Async注解使用的基本方法:

  1. 在方法上添加@Async注解;
  2. 所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象;
  3. 调用异步方法类上需要配置上注解@EnableAsync

结果发现理想很丰满,现实很骨感,并没有实现异步。原因有以下:

不要在同一个类里面调用异步方法

源码

public interface MyTestService {
  void sleep(int n);
  void test();
}

@Service
public class MyTestServiceImpl implements MyTestService {

  @Autowired
  private AsyncMyTestService asyncMyTestService;

  public void sleep(int n) {
    try {
      Thread.sleep(n * 1000);
    } catch (Exception e) {

    }
  }

  public void test() {
    System.out.println("test");
    asyncMethod();//同一个类里面调用异步方法
  }

  @Async
  public void asyncMethod() {
    sleep(10);
    System.out.println("async");
  }
}
 下面开始执行异步,发现异步没有生效。
	@Autowired
	private MyTestService myTestService;
	@Override
	public void run(String... args) throws Exception {
		long begin = System.currentTimeMillis();
		myTestService.test();
		System.out.println((System.currentTimeMillis() - begin) / 1000);
	}

原因是如果调用方和被调用方是在同一个类中,是无法产生切面的,@Async没有被Spring容器管理。

然后有些朋友就开始通过@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) 再通过AopContext.currentProxy()拿到当前Spring容器管理的Service的代理类 

((MyTestServiceImpl) AopContext.currentProxy()).asyncMethod();

再搜索一番解决如下报错,终于实现了异步。

java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

杜绝AopContext.currentProxy()

AopContext.currentProxy()破坏了面向接口的编程,也包含了很多潜在的坑,应该禁用。要用异步就好好新建一个单独的异步类。

 public interface AsyncMyTestService {
  void asyncMethod();
}


@Autowired
  private AsyncMyTestService asyncMyTestService;
  public void test() {
    System.out.println("test");
    asyncMyTestService.asyncMethod();
  }

没有定义线程池

如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。

定义通用线程池

@EnableAsync
@Configuration
public class ThreadTestConfig {
  @Bean
  public Executor taskExecutor() {
    ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
    pool.setCorePoolSize(5); //线程池活跃的线程数
    pool.setMaxPoolSize(10); //线程池最大活跃的线程数
    pool.setWaitForTasksToCompleteOnShutdown(true);
    pool.setThreadNamePrefix("lalala");
    return pool;
  }
}

@Async默认使用Bean Name为executor的线程池。也可以根据Bean Name指定特定线程池

@Async("taskExecuters2")
  public void asyncMethod() {
    sleep(10);
    System.out.println("async");
  }

循环依赖问题解决

A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象,Spring通过三级缓存解决循环依赖。详见:Spring-bean的循环依赖以及解决方式

但是如果A类里面方法使用了 @Async 注解,就引起了循环依赖报错,这是为什么呢。查阅官方文档Async注解初始化

NOTE: AsyncConfigurer configuration classes get initialized early in the application context bootstrap. If you need any dependencies on other beans there, make sure to declare them 'lazy' as far as possible in order to let them go through other post-processors as well.

在Spring 启动过程中Async注解为类创建代理类,这样Spring 三级缓存中就没有单例bean的早期引用对象而是Async代理对象,缓存里面找不到bean原始早期对象就引起报错。

解决方法是为注入的其他类增加懒加载@Lazy注解,解决循环依赖报错问题。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值