聊聊异步编程的七种实现方式

网站建设4年前发布
24 0 0

2023030612081671de01306dd40d507b28461525a04d3f779e3d868,大家好,我是 Tom哥,最近有很多小伙伴给我留言,能不能总结下异步编程,今天就和大家简单聊聊这个话题。,早期的系统是同步的,容易理解,我们来看个例子。,20230306121157216d68a0109919ec97938201df98923ca96ff8966,当用户创建一笔电商交易订单时,要经历的业务逻辑流程还是很长的,每一步都要耗费一定的时间,那么整体的RT就会比较长。,于是,聪明的人们开始思考能不能将一些非核心业务从主流程中剥离出来,于是有了异步编程雏形。,20230306120818845a3204237b14befd4563b6fe76939c193907309,核心思路:采用多线程优化性能,将串行操作变成并行操作。异步模式设计的程序可以显著减少线程等待,从而在高吞吐量场景中,极大提升系统的整体性能,显著降低时延。,接下来,我们来讲下异步有哪些编程实现方式。,直接继承 Thread类 是创建异步线程最简单的方式。,首先,创建Thread子类,普通类或匿名内部类方式;然后创建子类实例;最后通过start()方法启动线程。,当然如果每次都创建一个 Thread线程,频繁的创建、销毁,浪费系统资源。我们可以采用线程池。,将业务逻辑封装到 Runnable 或 Callable 中,交由 线程池 来执行。,20230306120819740e574288888bb7b4713954deb7178b82f971852,上述方式虽然达到了多线程并行处理,但有些业务不仅仅要执行过程,还要获取执行结果。,Java 从1.5版本开始,提供了 Callable 和 Future,可以在任务执行完毕之后得到任务执行结果。,当然也提供了其他功能,如:取消任务、查询任务是否完成等。,Future类位于java.util.concurrent包下,接口定义:,Future 表示一个可能还没有完成的异步任务的结果,通过 ​​get​​ 方法获取执行结果,该方法会阻塞直到任务返回结果。,FutureTask 实现了 RunnableFuture 接口,则 RunnableFuture 接口继承了 Runnable 接口和 Future 接口,所以可以将 FutureTask 对象作为任务提交给 ThreadPoolExecutor 去执行,也可以直接被 Thread 执行;又因为实现了 Future 接口,所以也能用来获得任务的执行结果。,FutureTask 常用来封装 Callable 和 Runnable,可以作为一个任务提交到线程池中执行。除了作为一个独立的类之外,也提供了一些功能性函数供我们创建自定义 task 类使用。,FutureTask 线程安全由CAS来保证。,20230306120819933c4e3401380fce460588379cdf1b6e22b62b979,如果是对多个任务多次自由串行、或并行组合,涉及多个线程之间同步阻塞获取结果,Future 代码实现会比较繁琐,需要我们手动处理各个交叉点,很容易出错。,Future 类通过 get() 方法阻塞等待获取异步执行的运行结果,性能比较差。,JDK1.8 中,Java 提供了 CompletableFuture 类,它是基于异步函数式编程。相对阻塞式等待返回结果,CompletableFuture 可以通过回调的方式来处理计算结果,实现了异步非阻塞,性能更优。,20230306120820b28ddff13eed1af38e96899c469bc1b249f36c320,(内容摘自:极客时间的《Java 并发编程实战》)。,CompletableFuture 提供了非常丰富的API,大约有50种处理串行,并行,组合以及处理错误的方法。,除了硬编码的异步编程处理方式,SpringBoot 框架还提供了 注解式 解决方案,以 方法体 为边界,方法体内部的代码逻辑全部按异步方式执行。,首先,使用 @EnableAsync 启用异步注解。,自定义线程池:,在异步处理的方法上添加注解 @Async ,当对 execute 方法 调用时,通过自定义的线程池 defaultThreadPoolExecutor 异步化执行  execute 方法。,用 @Async 注解标记的方法,称为异步方法。在spring boot应用中使用 @Async 很简单:,事件机制在一些大型项目中被经常使用,Spring 专门提供了一套事件机制的接口,满足了架构原则上的解耦。,ApplicationContext 通过 ApplicationEvent 类和 ApplicationListener 接口进行事件处理。如果将实现 ApplicationListener 接口的 bean 注入到上下文中,则每次使用 ApplicationContext 发布 ApplicationEvent 时,都会通知该 bean。本质上,这是标准的观察者设计模式。,首先,自定义业务事件子类,继承自 ApplicationEvent,通过泛型注入业务模型参数类。相当于 MQ 的消息体。,然后,编写事件监听器。ApplicationListener 接口是由 Spring 提供的事件订阅者必须实现的接口,我们需要定义一个子类,继承 ApplicationListener。相当于 MQ 的消费端。,最后,发布事件,把某个事件告诉所有与这个事件相关的监听器。相当于 MQ 的生产端。,上面是跑了个demo的运行结果,我们发现无论生产端还是消费端,使用了同一个线程 http-nio-8090-exec-1,Spring 框架的事件机制默认是同步阻塞的。只是在代码规范方面做了解耦,有较好的扩展性,但底层还是采用同步调用方式。,那么问题来了,如果想实现异步调用,如何处理?,我们需要手动创建一个 SimpleApplicationEventMulticaster,并设置 TaskExecutor,此时所有的消费事件采用异步线程执行。,我们看下改造后的运行结果:,SimpleApplicationEventMulticaster 这个我们自己实例化的 Bean 与系统默认的加载顺序如何?会不会有冲突?,查了下 Spring 源码,处理逻辑在 AbstractApplicationContext#initApplicationEventMulticaster 方法中,通过 beanFactory 查找是否有自定义的 Bean,如果没有,容器会自己 new 一个 SimpleApplicationEventMulticaster 对象注入到容器中。,20230306121156a247f6990d10d995bca485166db60574123517256,异步架构是互联网系统中一种典型架构模式,与同步架构相对应。而消息队列天生就是这种异步架构,具有超高吞吐量和超低时延。,消息队列异步架构的主要角色包括消息生产者、消息队列和消息消费者。,20230306120821b588e7b30862a827657907b63c4ef41d530d05201,消息生产者就是主应用程序,生产者将调用请求封装成消息发送给消息队列。,消息队列的职责就是缓冲消息,等待消费者消费。根据消费方式又分为点对点模式和发布订阅模式两种。,消息消费者,用来从消息队列中拉取、消费消息,完成业务逻辑处理。,当然市面上消息队列框架非常多,常见的有RabbitMQ、Kafka、RocketMQ、ActiveMQ 和 Pulsar 等。,202303061211582378fc3438f860fcc47539d78e511c82567aae676,不同的消息队列的功能特性会略有不同,但整体架构类似,这里就不展开了。,我们只需要记住一个关键点,借助消息队列这个中间件可以高效的实现异步编程。

© 版权声明

相关文章