微信公众号地址:SpringBoot常见异步编程,你会多少?
前言:异步执行对于开发者来说并不陌生,在实际的开发过程中,很多场景都会使用到异步,相比同步执行,异步可以大大缩短请求链路耗时时间,比如:「发送短信、消息、邮件、异步更新、缓存一致性等」,这些都是典型的可以通过异步实现的场景。
一、什么是异步编程?
异步编程是让程序并发运行的一种手段。它允许多个事件同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行。
核心思路:采用多线程优化性能,将串行操作变成并行操作。异步模式设计的程序可以显著减少线程等待,从而在高吞吐量场景中,极大提升系统的整体性能,显著降低时延。
二、线程Thread
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("执行业务逻辑...");
}
});
}
三、异步框架CompletableFuture–jdk1.8
java中提供的一个异步执行类,有些业务不仅要执行过程,还要获取执行结果。Future类通过get()方法阻塞等待获取异步执行的运行结果,性能比较差。JDK1.8中,Java提供了CompletableFuture类,它是基于异步函数式编程。相对阻塞式等待返回结果,CompletableFuture可以通过回调的方式来处理计算结果,实现了异步非阻塞,性能更优。(作用在某个代码块上)
优点:1、异步任务结束时,会自动回调某个对象的方法;
2、异步任务出错时,会自动回调某个对象的方法;
- 3、主线程设置好回调后,不再关心异步任务的执行。
参考前面文章:
1、研发必会-异步编程利器之CompletableFuture(上)
2、研发必会-异步编程利器之CompletableFuture(含源码中)
优点:
1、更加灵活,可以配置线程池等异步执行的相关参数。
2、可以通过实现AsyncUncaughtExceptionHandler接口来捕获异步执行中的异常。
缺点:
1、需要在代码中手动创建线程池,相对比较繁琐。
2、方法必须返回Future或CompletableFuture等类型,否则无法异步执行。
四、SpringBoot注解@Async
SpringBoot框架还提供了注解式解决方案,以方法体为边界,方法体内部的代码逻辑全部按异步方式执行,当调用方法会单独开启一个线程进行调用。用@Async注解标记的方法,称为异步方法。在需要被异步调用的方法外加上@Async。注意:所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象。(作用在方法体上,不能直接获取异步结果)
注意:@Async的粒度比较大,在使用的时候只能添加到方法上,并且需要自定义线程池。
具体实现方式参考前面的文章:SpringBoot使用@Async实现多线程异步
在实际项目中,推荐使用@Async的自定义线程池,不推荐直接使用@Async直接实现异步。
例如:
优点:
1、简单易用,只需要在方法上添加@Async注解即可。
2、依赖Spring框架,集成度高,可以与其他Spring组件无缝协作。
缺点:
1、方法必须是public,否则异步执行无效。
2、不能直接获取异步执行结果,需要使用Future或
CompletableFuture等类服务器托管网型。
五、消息队列MQ
消息队列天生是异步架构,具有超高吞吐量和超低时延。消息队列异步架构的主要角色包括消息生产者、消息队列和消息消费者。借助消息队列这个中间件可以高效地实现异步编程。
常见MQ有:
具体实现方式参考前面的文章:(RabbitMQ后面会专题讲述,敬请期待)
1、Redis实现消息队列
六、Spring的ApplicationEvent事件实现异步
定义事件:
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实现异步的时候,当程序出现异常错误的时候,需要考虑补偿机制,这时候可以结合SpringRetry重试来帮助我们避免这种异常造成数据不一致问题。
七、Hutool的ThreadUtil异步工具类
Hutool的ThreadUtil异步工具类,是第三方异步框架。并发的核心在线程。
参考:https://www.hutool.cn/docs/#/core
代码参考:
@Slf4j
public class ThreadUtils {
public static void main(String[] args) {
for (int i = 0; i > {
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
int number = threadLocalRandom.nextInt(20) + 1;
System.out.println(number);
});
log.info("当前第:" + i + "线程");
}
log.info("task finish!");
}
}
八、Guava异步
Guava的ListenableFuture顾名思义就是可以监听的Future,是对java原生Future的扩展增强。Future表示一个异步计算任务,当任务完成时可以得到计算结果。如果希望一旦计算完成就拿到结果展示给用户或者做另外的计算,就必须使用另一个线程不断的查询计算状态。这样做,代码复杂,而且效率低下。使用「GuavaListenableFuture」可以帮检测Future是否完成,不需要再通过get()方法等待异步的计算结果,如果完成就自动调用回调函数,这样可以减少并发程序的复杂度。
ListenableFuture是一个接口,它从jdk的Future接口继承,添加了voidaddListener(Runnablelistener,Executorexecutor)方法。
定义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实现异步任务
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
如果大家对相关文章感兴趣,可以关注微信公众号”程序猿小杨“,会持续更新优秀文章!欢迎大家分享、收藏、点赞、在看,您的支持就是我坚持下去的最大动力!谢谢!
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 视频云存储/安防监控/AI分析/视频AI智能分析网关:垃圾满溢AI算法
随着服务器托管网我国科技的发展和城市化进程加快,大家对于生活环境以及空气质量更加重视,要求越来越严格。城市街道垃圾以及生活区垃圾满溢已经成为城市之痛。乱扔垃圾,垃圾不入桶这些行为已经严重影响到了城市的美化问题。特别是炎热的夏日和雨水季节,大量垃圾堆放会释放有毒…