锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. JAVA
  4. Springboot中使用@Async注解7大失效场景及解决方案

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

0
  • JAVA
  • 发布于 2024-08-09
  • 0 次阅读
黄健
黄健

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

前言

在 Spring Boot 中,@Async 注解就像一把瑞士军刀,能帮你轻松处理那些耗时的任务,让主线程可以继续忙别的事儿。
不过,跟所有强大的工具一样,用不好它也可能出岔子。
有时候,你可能因为线程池没配好、异常没处理好,或者 Spring 代理没生效等原因,导致 @Async 没按你期望的那样工作。
为了避免这些坑,咱们得深入了解下 @Async 是怎么工作的,还要知道怎么用才能不出问题。
接下来,咱们就来聊聊 7 种 常见的 @Async 失效情况,还有怎么搞定它们。这样,大家在享受异步编程带来的好处时,也能心里更有底儿。

本文,已收录于,我的技术网站 aijiangsir.com,有 BAT 大厂完整面经,工作技术,架构师成长之路,等经验分享
最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的, 7701 页的 BAT 大佬写的刷题笔记,让我 offer 拿到手软

正文

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

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

@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 等注解标注

例如:

//一定使用@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 注解同样不会生效。

例如:

// 没有使用 @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 注解,启用异步支持。

例如:

@Configuration
@EnableAsync
public class AsyncConfig {  
    // ... 其他配置 ...  
}

场景三:方法不是 public 的

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

例如:

@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 注解时,如果没有正确配置线程池,可能会遇到异步任务没有按预期执行的情况。
例如,线程池被配置为只有一个线程,且该线程一直被占用,那么新的异步任务就无法执行。

例如:

@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.");  
    }  
}

解决方案:

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

场景五:异常处理不当

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

例如:

@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 注解无效。

例如:

@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 关键字创建

@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 注解失效,也就是事务失效。
例如:

@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 标识

@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,有大厂完整面经,工作技术,架构师成长之路,等经验分享

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

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

标签: #JAVA 991
相关文章

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

SpringBoot整合异步任务执行 2024-10-08 11:24

同步任务: 同步任务是在单线程中按顺序执行,每次只有一个任务在执行,不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务,无法同时处理多个任务,响应慢,影响用 户体验 异步任务: 异步任务是在多线程中同时执行,多个任务可以并发执行,同时处理多个请求,响应快,资源

springboot kafka多数据源,通过配置动态加载发送者和消费者 2024-10-08 11:24

前言 最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需要对架构做一定的设计了。 准备test kafka本身非常容易上手,如果我们需要单元测试,引入ja

SpringBoot 集成 Redis 2024-10-08 11:24

一:SpringBoot 集成 Redis ①Redis是一个 NoSQL(not only)数据库, 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器;常用的数据类型: string , hash ,set ,zset , list ③通过Redis客户端可以使用多种语

SpringBoot整合QQ邮箱 2024-10-08 11:24

SpringBoot可以通过导入依赖的方式集成多种技术,这当然少不了我们常用的邮箱,现在本章演示SpringBoot整合QQ邮箱发送邮件…. 下面按步骤进行: 1.获取QQ邮箱授权码 1.1 登录QQ邮箱 1.2 开启SMTP服务 找到下图中的SMTP服务区域,如果当前账号未开启的话自己手动开启。

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.