微信公众号访问地址:SpringBoot常见异步编程,你会多少?
近期热推文章:
1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表;
2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据;
3、基于Redis实现关注、取关、共同关注及消息推送(含源码)
4、SpringBoot整合多数据源,并支持动态新增与切换(详细教程)
5、SpringBoot使用@Async实现多线程异步
6、研发必会-异步编程利器之CompletableFuture(含源码 中)
前言:异步执行对于开发者来说并不陌生,在实际的开发过程中,很多场景都会使用到异步,相比同步执行,异步可以大大缩短请求链路耗时时间,比如:「发送短信、消息、邮件、异步更新、缓存一致性等」,这些都是典型的可以通过异步实现的场景。
异步编程是让程序并发运行的一种手段。它允许多个事件同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行。
核心思路:采用多线程优化性能,将串行操作变成并行操作。异步模式设计的程序可以显著减少线程等待,从而在高吞吐量场景中,极大提升系统的整体性能,显著降低时延。
public class AsyncThread extends Thread {
@Override
public void run() {
System.out.println("Current thread name:" + Thread.currentThread().getName() + " Send email success!");
}
public static void main(String[] args) {
AsyncThread asyncThread = new AsyncThread();
asyncThread.run();
}
若每次都创建一个Thread线程,频繁创建、销毁,浪费系统资源,可以采用线程池,将业务逻辑封装到Runnable或Callable中,交由线程池来执行。
private ExecutorService executorService = Executors.newCachedThreadPool();
public void fun() {
executorService.submit(new Runnable() {
@Override
public void run() {
log.info("执行业务逻辑...");
}
});
}
java中提供的一个异步执行类,有些业务不仅要执行过程,还要获取执行结果。Future 类通过 get() 方法阻塞等待获取异步执行的运行结果,性能比较差。JDK1.8 中,Java 提供了 CompletableFuture 类,它是基于异步函数式编程。相对阻塞式等待返回结果,CompletableFuture 可以通过回调的方式来处理计算结果,实现了异步非阻塞,性能更优。(作用在某个代码块上)
优点:1、异步任务结束时,会自动回调某个对象的方法;
2、异步任务出错时,会自动回调某个对象的方法;
3、主线程设置好回调后,不再关心异步任务的执行。
参考前面文章:
1、研发必会-异步编程利器之CompletableFuture(上)
2、研发必会-异步编程利器之CompletableFuture(含源码 中)
优点:
1、更加灵活,可以配置线程池等异步执行的相关参数。
2、可以通过实现AsyncUncaughtExceptionHandler接口来捕获异步执行中的异常。
缺点:
1、需要在代码中手动创建线程池,相对比较繁琐。
2、方法必须返回Future或CompletableFuture等类型,否则无法异步执行。
SpringBoot 框架还提供了注解式解决方案,以方法体为边界,方法体内部的代码逻辑全部按异步方式执行,当调用方法会单独开启一个线程进行调用。用 @Async 注解标记的方法,称为异步方法。在需要被异步调用的方法外加上 @Async。注意:所使用的 @Async 注解方法的类对象应该是Spring容器管理的bean对象。(作用在方法体上,不能直接获取异步结果)
注意:@Async的粒度比较大,在使用的时候只能添加到方法上,并且需要自定义线程池。
具体实现方式参考前面的文章:SpringBoot使用@Async实现多线程异步
在实际项目中, 推荐使用@Async的自定义线程池,不推荐直接使用@Async直接实现异步。
例如:
优点:
1、简单易用,只需要在方法上添加@Async注解即可。
2、依赖Spring框架,集成度高,可以与其他Spring组件无缝协作。
缺点:
1、方法必须是public,否则异步执行无效。
2、不能直接获取异步执行结果,需要使用Future或
CompletableFuture等类型。
消息队列天生是异步架构,具有超高吞吐量和超低时延。消息队列异步架构的主要角色包括消息生产者、消息队列和消息消费者。借助消息队列这个中间件可以高效地实现异步编程。
常见MQ有:
具体实现方式参考前面的文章:(RabbitMQ后面会专题讲述,敬请期待)
1、Redis实现消息队列
定义事件:
public class AsyncSendEmailEvent extends ApplicationEvent {
private String email; //邮箱
private String subject; //主题
private String content; //内容
private String targetUserId; //接收者
}
定义事件处理器:
@Slf4j
@Component
public class AsyncSendEmailEventHandler
implements ApplicationListener
{ @Autowired
private IMessageHandler mesageHandler;
@Async("taskExecutor")
@Override
public void onApplicationEvent(AsyncSendEmailEvent event) {
if (event == null) {
return;
}
String email = event.getEmail();
String subject = event.getSubject();
String content = event.getContent();
String targetUserId = event.getTargetUserId();
mesageHandler.sendsendEmailSms(email, subject, content, targerUserId);
}
}
可能有时采用ApplicationEvent实现异步的时候,当程序出现异常错误的时候,需要考虑补偿机制,这时候可以结合Spring Retry重试来帮助我们避免这种异常造成数据不一致问题。
Hutool的ThreadUtil异步工具类,是第三方异步框架。并发的核心在线程。
参考:https://www.hutool.cn/docs/#/core
代码参考:
@Slf4j
public class ThreadUtils {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
ThreadUtil.execAsync(() -> {
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
int number = threadLocalRandom.nextInt(20) + 1;
System.out.println(number);
});
log.info("当前第:" + i + "线程");
}
log.info("task finish!");
}
}
Guava的ListenableFuture顾名思义就是可以监听的Future,是对java原生Future的扩展增强。Future表示一个异步计算任务,当任务完成时可以得到计算结果。如果希望一旦计算完成就拿到结果展示给用户或者做另外的计算,就必须使用另一个线程不断的查询计算状态。这样做,代码复杂,而且效率低下。使用「Guava ListenableFuture」可以帮检测Future是否完成,不需要再通过get()方法等待异步的计算结果,如果完成就自动调用回调函数,这样可以减少并发程序的复杂度。
ListenableFuture是一个接口,它从jdk的Future接口继承,添加了void addListener(Runnable listener, Executor executor)方法。
定义ListenableFuture的实例:
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
final ListenableFuture
listenableFuture = executorService.submit(new Callable () { @Override
public Integer call() throws Exception {
log.info("callable execute...")
TimeUnit.SECONDS.sleep(1);
return 1;
}
}
);
首先通过MoreExecutors类的静态方法listeningDecorator方法初始化一个ListeningExecutorService的方法,然后使用此实例的submit方法即可初始化ListenableFuture对象。
ListenableFuture要做的工作,在Callable接口的实现类中定义,这里只是休眠了1秒钟然后返回一个数字1,有了ListenableFuture实例,可以执行此Future并执行Future完成之后的回调函数。
参考案例:
Futures.addCallback(listenableFuture, new FutureCallback
() { @Override
public void onSuccess(Integer result) {
//成功执行...
System.out.println("Get listenable future's result with callback " + result);
}
@Override
public void onFailure(Throwable t) {
//异常情况处理...
t.printStackTrace();
}
});
TaskExecutor 是 Spring 提供的一个接口,它定义了一个方法 execute(),用来执行异步任务。使用 TaskExecutor 需要满足以下条件:
1、需要在 Spring 配置文件中配置一个 TaskExecutor 实例;
2、在异步方法中调用 execute() 方法来执行异步任务。
具体实现方式参考前面的文章:
1、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据
2、SpringBoot用线程池ThreadPoolExecutor处理百万级数据
优点:
1、基于Spring内置的线程池实现,省去手动配置线程池的繁琐过程。
2、可以设置队列容量和拒绝策略,控制异步任务的执行顺序和并发量。
缺点:
略微复杂,需要手动创建ThreadPoolTaskExecutor实例,并将其注入到需要异步执行的方法中。
最后总结:
异步编程是一种强大的编程技术,可以处理并发任务时显著提升性能和响应性。将耗时的操作放在后台执行,异步编程使主线程继续执行其他任务而不需要等待。
在现代应用程序中,异步编程已变得愈发重要,特别是在涉及:网络请求、数据库操作、IO操作或计算密集型任务时。通过使用异步编程,我们可以充分利用多核处理器和并发性,最大程度地提高程序的效率。
然而,异步编程也需谨慎使用,特别是在处理复杂的并发逻辑时。在异步代码中,需要注意处理线程安全性和竞态条件,以避免潜在的并发问题。
参考文章:
https://blog.csdn.net/justlpf/article/details/130681028
https://www.jb51.net/program/285954ybr.htm
如果大家对相关文章感兴趣,可以关注微信公众号"程序猿小杨",会持续更新优秀文章!欢迎大家 分享、收藏、点赞、在看,您的支持就是我坚持下去的最大动力!谢谢!