Springboot中使用@Async注解7大失效场景及解决方案

前言

在Spring Boot中,@Async注解就像一把瑞士军刀,能帮你轻松处理那些耗时的任务,让主线程可以继续忙别的事儿。

不过,跟所有强大的工具一样,用不好它也可能出岔子。

有时候,你可能因为线程池没配好异常没处理好 ,或者Spring代理没生效 等原因,导致@Async没按你期望的那样工作。

为了避免这些坑,咱们得深入了解下@Async是怎么工作的,还要知道怎么用才能不出问题。

接下来,咱们就来聊聊 7种 常见的@Async失效情况,还有怎么搞定它们。这样,大家在享受异步编程带来的好处时,也能心里更有底儿。

本文,已收录于,我的技术网站 aijiangsir.com,有BAT大厂完整面经,工作技术,架构师成长之路,等经验分享

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。

这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

正文

场景一:调用者与被调用者在同一个类中

当调用 @Async 注解的方法的类和被调用的方法在同一个类中时,@Async 注解不会生效。因为 Spring 的 AOP 代理是基于接口的,对于同一个类中的方法调用,不会经过代理,因此 @Async 注解不会被处理。

例如:

java 复制代码
@Service  
public class MyService {  
  
    @Async  
    public void asyncMethod() {  
        // 模拟耗时操作  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Async method executed.");  
    }  
  
    public void callAsyncMethod() {  
        asyncMethod(); // 直接调用,不会异步执行  
    }  
}

解决方案:

1、确保异步方法和调用它的方法不在同一个类中。可以将异步方法提取到一个单独的 Service 中,并在需要的地方注入这个 Service。

2、确保异步方法的执行类(即包含 @Async 注解方法的类)被 Spring 容器管理,比如通过 @Service、@Component 等注解标注

例如:

java 复制代码
//一定使用@Service、@Component 等注解标注,确保执行类被Spring管理,因为异步线程是通过动态代理实现的
@Service
public class AsyncService {
    @Async  
    public void asyncMethod() {  
        // 模拟耗时操作  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Async method executed.");  
    }  
}

场景二:配置类未启用异步支持

如果配置类中没有启用异步支持,即没有使用 @EnableAsync 注解,那么 @Async 注解同样不会生效。

例如:

java 复制代码
// 没有使用 @EnableAsync 注解,因此不会启用异步支持  
@Configuration  
public class AsyncConfig {  
    // ... 其他配置 ...  
}  
  
@Service  
public class MyService {  
  
    @Async  
    public void asyncMethod() {  
        // 模拟耗时操作  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Async method executed.");  
    }  
}

解决方案:

1、在配置类上使用 @EnableAsync 注解,启用异步支持。

例如:

java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig {  
    // ... 其他配置 ...  
}

场景三:方法不是 public 的

@Async 注解的方法必须是 public 的,否则不会被 Spring AOP 代理捕获,导致异步执行不生效。

例如:

java 复制代码
@Service  
public class MyService {  
  
    @Async // 但这个方法不是 public 的,所以 @Async 不会生效  
    protected void asyncMethod() {  
        // 模拟耗时操作  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Async method executed.");  
    }  
  
    public void callAsyncMethod() {  
        asyncMethod(); // 直接调用,但由于 asyncMethod 不是 public 的,因此不会异步执行  
    }  
}

解决方案:

确保异步方法是 public 的

场景四:线程池未正确配置

在使用 @Async 注解时,如果没有正确配置线程池,可能会遇到异步任务没有按预期执行的情况。

例如,线程池被配置为只有一个线程,且该线程一直被占用,那么新的异步任务就无法执行。

例如:

java 复制代码
@Configuration  
@EnableAsync  
public class AsyncConfig implements AsyncConfigurer {  
  
    @Override  
    public Executor getAsyncExecutor() {  
        // 创建一个只有一个线程的线程池,这会导致并发问题  
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
        executor.setCorePoolSize(1);  
        executor.setMaxPoolSize(1);  
        executor.setQueueCapacity(10);  
        executor.setThreadNamePrefix("Async-");  
        executor.initialize();  
        return executor;  
    }  
  
    // ... 其他配置 ...  
}
  
@Service  
public class MyService {  
  
    @Async  
    public void asyncMethod() {  
        // 模拟耗时操作  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Async method executed.");  
    }  
}

解决方案:

正确配置线程池:确保线程池配置合理,能够处理预期的并发任务量

场景五:异常处理不当

如果在异步方法中抛出了异常,并且没有妥善处理,那么这个异常可能会导致任务失败,而调用者可能无法感知到异常的发生。

例如:

java 复制代码
@Service  
public class MyService {  
  
    @Async  
    public void asyncMethod() {  
        // 模拟一个可能会抛出异常的耗时操作  
        throw new RuntimeException("Async method exception");  
    }  
}
// 调用者  
@Service  
public class CallerService {  
  
    @Autowired  
    private MyService myService;  
  
    public void callAsyncMethod() {  
        myService.asyncMethod(); // 调用异步方法,但如果该方法抛出异常,调用者不会立即感知到  
    }  
}

解决方案:

合理处理异常:在异步方法中妥善处理异常,可以通过 Future 对象来捕获异步任务执行过程中抛出的异常。

场景六:Spring代理未生效

如果通过 new 关键字直接创建了服务类的实例,而不是通过 Spring 容器来获取,那么 Spring 的 AOP 代理将不会生效,导致 @Async 注解无效。

例如:

java 复制代码
@Service  
public class MyService {  
  
    @Async  
    public void asyncMethod() {  
        // 模拟耗时操作  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Async method executed.");  
    }  
}  
  
public class SomeNonSpringClass {  
  
    public void someMethod() {  
        MyService myService = new MyService(); // 直接通过 new 创建 MyService 实例,不会经过 Spring 代理  
        myService.asyncMethod(); // 这里 @Async 不会生效  
    }  
}

解决方案:

合理利用依赖注入:始终通过 Spring 容器来获取服务类的实例,而不是直接通过 new 关键字创建

java 复制代码
@Service  
public class MyService {  
    @Async  
    public void asyncMethod() {  
        // 模拟耗时操作  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Async method executed.");  
    }  
}  
@Service
public class SomeNonSpringClass {  
    @Autowired  
    private MyService myService;  
    public void someMethod() {   
        myService.asyncMethod(); // 这里 @Async 会生效  
    }  
}

场景七:使用 @Transactional 与 @Async 同时注解方法,导致事务失效

在同一个方法上同时使用 @Transactional 和 @Async 注解可能会导致问题。

由于 @Async 会导致方法在一个新的线程中执行,而 @Transactional 通常需要在一个由 Spring 管理的事务代理中执行,这两个注解的结合使用可能会导致事务管理失效或行为不可预测。

此种场景不会导致@Async注解失效,但是会导致@Transactional注解失效,也就是事务失效。

例如:

java 复制代码
@Service  
public class MyService {  
  
    @Autowired  
    private MyRepository myRepository;  
  
    // 错误的用法:同时使用了 @Transactional 和 @Async  
    @Transactional  
    @Async  
    public void asyncTransactionalMethod() {  
        // 模拟一个数据库操作  
        myRepository.save(new MyEntity());  
          
        // 模拟可能抛出异常的代码  
        if (true) {  
            throw new RuntimeException("Database operation failed!");  
        }  
    }  
}  
  
@Repository  
public interface MyRepository extends JpaRepository<MyEntity, Long> {  
    // ...  
}  
  
@Entity  
public class MyEntity {  
    // ... 实体类的属性和映射 ...  
}

上面的代码,在抛出异常的时候,我们期望的是回滚前面的数据库保存操作,但是因为事务失效,会导致错误数据成功保存进数据库。

解决方案:

正确配置事务,比如单独提取事务执行的逻辑到一个新的Service里,事务执行方法单独使用@Transactional标识

java 复制代码
@Service  
public class MyService {  
  
    @Autowired  
    private MyTransactionalService myTransactionalService;  
  
    @Autowired  
    private AsyncExecutor asyncExecutor;  
  
    public void callAsyncTransactionalMethod() {  
        // 在事务中执行数据库操作  
        MyEntity entity = myTransactionalService.transactionalMethod();  
          
        // 异步执行其他操作  
        asyncExecutor.execute(() -> {  
            // 这里执行不需要事务管理的异步操作  
            // ...  
        });  
    }  
}  
  
@Service  
public class MyTransactionalService {  
  
    @Autowired  
    private MyRepository myRepository;  
  
    @Transactional  
    public MyEntity transactionalMethod() {  
        // 在事务中执行数据库操作  
        return myRepository.save(new MyEntity());  
    }  
}  
  
@Component  
public class AsyncExecutor {  
  
    @Async  
    public void execute(Runnable task) {  
        task.run();  
    }  
}

总结

这里面,绝大多数人会遇到的坑点主要会集中在没有配置自定义线程池、异步方法在同一个类中调用、事务不起作用这几个问题上。

所以,万金油的写法还是专门定义一个AsyncService,将异步方法都写在里面,需要使用的时候,就在其他类将其注入即可。

最后说一句(求关注,求赞,别白嫖我)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。

这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

本文,已收录于,我的技术网站 aijiangsir.com,有大厂完整面经,工作技术,架构师成长之路,等经验分享

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激!

相关推荐
Python私教2 小时前
model中能定义字段声明不存储到数据库吗
数据库·oracle
吾日三省吾码3 小时前
JVM 性能调优
java
弗拉唐4 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi774 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
BestandW1shEs4 小时前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师4 小时前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球4 小时前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...4 小时前
表的操作(MySQL)
数据库·mysql·表的操作
哥谭居民00014 小时前
MySQL的权限管理机制--授权表
数据库