Java 中实现异步的主要原因是为了提高程序的性能和响应速度。如果程序中存在大量的IO操作或者其他需要长时间等待的操作(如网络请求、文件读写等),使用同步方式会导致线程长时间阻塞,降低系统的并发能力和响应速度。而使用异步方式可以将等待时间利用起来,让 CPU 在等待 IO 操作完成时去执行其他任务,从而提高系统的并发性能和响应速度。
Java实现异步的原因主要有以下几点:
同步:
调用方在调用过程中,持续阻塞,一直到返回结果。
同步获取结果的方式是: 主动等待。
异步:
调用方在调用过程中,不会阻塞, 不直接等待返回结果, 而是执行其他任务。
异步获取结果的方式是 : 被动通知或者 被动回调。
场景1: 超高并发 批量 写 mysql 、批量写 elasticSearch
场景2: 超高并发 批量 IO
场景3: 超高并发 发送短信、发邮件
场景4: 超高并发 发送消息
场景5: 超高吞吐 生产者、 消费者 场景
场景6: 超高吞吐 发布、 订阅 场景
场景7: 分布式的 通知场景
场景8:异步回调场景
场景9:其他的 异步场景, 不知道能列举出多少,总之非常多
在 Java 中,异步编程通常用于以下场景:
在 Java 中实现异步的方式有以下几种:
以上就是Java实现异步编程的一些常见原因和实现方式。具体选择哪种方式取决于你的应用场景和需求。
新建线程Thread 实现异步
线程池化 实现异步
Future 阻塞式异步
guava 回调式异步
Netty 回调式异步
Servlet 3.0 异步
CompletableFuture 回调式异步
JDK 9 Flow 响应式编程
RxJava 响应式 异步
Reactor 响应式 异步
Spring注解@Async 异步
EventBus 框架 发布订阅模式异步
Spring ApplicationEvent 事件 发布订阅模式
RocketMq 消息队列 分布式 发布订阅模式(Pub/Sub) 异步
Redis 消息队列 分布式 发布订阅模式(Pub/Sub) 异步
Distruptor 框架异步
ForkJoin 框架异步
RocketMQ源码中ServiceThread 能急能缓的高性能异步
方式3:Future 阻塞式异步
为了获取异步线程的返回结果,以及更好的对异步线程的干预,Java在1.5版本之后提供了一种新的多线程的创建方式—— FutureTask方式。
FutureTask方式包含了一系列的Java相关的类,处于java.util.concurrent包中。
使用FutureTask方式进行异步调用时,所涉及的重要组件为FutureTask类和Callable接口。
Future 的调用方式,属于阻塞式异步
主要原因在于,在获取异步线程处理结果时,需要主线程主动通过Future.get() 去获取,
如果异步线程没有执行完,那么Future.get() 会阻塞 调用线程,一直到超时。
阻塞式异步Future的不足之处
Future的不足之处的包括以下几点:
无法被动接收异步任务的计算结果:
虽然我们可以主动将异步任务提交给线程池中的线程来执行,但是待异步任务执行结束之后,主线程无法得到任务完成与否的通知,它需要通过get方法主动获取任务执行的结果。
Future件彼此孤立:
有时某一个耗时很长的异步任务执行结束之后,你想利用它返回的结果再做进一步的运算,该运算也会是一个异步任务,两者之间的关系需要程序开发人员手动进行绑定赋予,Future并不能将其形成一个任务流(pipeline),每一个Future都是彼此之间都是孤立的,所以才有了后面的CompletableFuture,CompletableFuture就可以将多个Future串联起来形成任务流。
Futrue没有很好的错误处理机制:
截止目前,如果某个异步任务在执行发的过程中发生了异常,调用者无法被动感知,必须通过捕获get方法的异常才知晓异步任务执行是否出现了错误,从而在做进一步的判断处理。
方式4:guava 回调式异步
由于JDK在1.8之前没有 回调式异步组件,于是出现了很多 开源的 回调式异步组件。
比较常用的是 guava 的回调式异步。
Guava是Google提供的Java扩展包,它提供了一种异步回调的解决方案。
Guava中与异步回调相关的源码处于com.google.common.util.concurrent包中。
包中的很多类都用于对java.util.concurrent的能力扩展和能力增强。
比如,Guava的异步任务接口ListenableFuture扩展了Java的Future接口,实现了异步回调的的能力。
方式5:Netty 回调式异步
由于JDK在1.8之前没有 回调式异步组件,于是出现了很多 开源的 回调式异步组件。
Netty 也算其中之一。
Netty 是 一个 著名的高性能NIO王者框架, 是 IO 的王者组件。 具体,请参见尼恩梳理的四大王者组件。
Netty 除了作为NIO框架之王,其子模也是可以单独使用的,比如说异步回调模块。
Netty 的 回调式异步组件 更加牛掰,为啥呢?
通过Netty源码可以知道: Netty 的 回调式异步组件不光提供了外部的回调监听设置,而且可以在异步代码中, 通过Promise接口,可以对回调结果进行干预,比如说在进行回调之前,执行一些其他的操作。
方式6:Servlet 3.0 异步
梳理一下: Callback 异步回调的使用场景。
Callback 真正体现价值,是它与 NIO 技术结合之后。
CPU 密集型场景,采用 Callback 回调没有太多意义;
IO 密集型场景,如果是使用 BIO模式,Callback 同样没有意义,因为一个连接一个线程,IO线程是因为 IO 而阻塞。
IO 密集型场景,如果是使用 NIO 模式,使用Callback 才有意义。 NIO是少量IO线程负责大量IO通道,IO线程需要避免线程阻塞,所以,也必须使用 Callback ,才能使应用得以被开发出来。
所以,高性能的 NIO 框架如 Netty ,都是基于 Callback 异步回调的。
但是,在微服务流行的今天,Netty 却没有在WEB服务器中占据统治地位。
微服务系统中,多级服务调用很常见,一个服务先调 A,再用结果 A 调 B,然后用结果 B 调用 C,等等。
如果使用Netty 作为底层服务器,IO 线程能大大降低,能处理的连接数(/请求数)也能大大增加,那么,为啥Netty 却没有在WEB服务器中占据统治地位呢?
—这其中的难度来自两方面:
一是 NIO 和 Netty 本身的技术难度,
二是 Callback hell:Callback 风格所导致的代码理解和维护的困难。
因此,Netty 通常用于在基础架构层面,在业务系统中应用较少。
这也是大厂小伙人人要求 精通Netty,而中小厂小伙伴,不怎么认识Netty的原因。
当然,作为IO之王,学习Netty对应提升大家的内功,是至关重要的。
Netty的知识,具体请参见《Java高并发核心编程 卷1 加强版》,很多小伙伴,靠此书入的门。
直接使用 Netty 开发WEB应用会遇到技术难度挑战、以及 Callback Hell 问题。
所以,Servlet 3.0 提供了一个异步解决方案。
方式7:回调式 异步CompletableFuture
JDK 1.8之前并没有实现回调式的异步,CompletableFuture是JDK 1.8引入的实现类,实现了JDK内置的异步回调模式异步。
CompletableFuture的创新是:通过 链式调用,解决 Callback Hell(回调地狱)问题, 让代码变得的可理解行更强,可读性 更强。
CompletableFuture 该类实现了Future和CompletionStage两个接口。
该类的实例作为一个异步任务,可以在自己异步执行完成之后触发一些其他的异步任务,从而达到异步回调的效果。
方式8:JDK 9 Flow 响应式编程
但是 JDK 8 的 CompletableFuture 属于链式调用,它在形式上带有一些响应式编程的函数式代码风格。
因为 Callback Hell 对代码可读性有很大杀伤力,从开发人员的角度来讲,反应式编程技术和链式调用一样,使得代码可读性要比 Callback 提升了许多。
响应式流从2013年开始,作为提供非阻塞背压的异步流处理标准的倡议。
Reactive Stream是一套基于发布/订阅模式的数据处理规范。
更确切地说,Reactive流目的是“找到最小的一组接口,方法和协议,用来描述必要的操作和实体以实现这样的目标:以非阻塞背压方式实现数据的异步流”。
**响应式流(Reactive Streams)**是一个响应式编程的规范,用来为具有非阻塞背压(Back pressure)的异步流处理提供标准,用最小的一组接口、方法和协议,用来描述必要的操作和实体。这里涉及到一个关键概念叫 Backpressure,国内大部分翻译为背压,我们先来了解这是什么。
响应式编程,其实就是对数据流的编程,而对流的处理对数据流的变化进行响应,是通过异步监听的方式来处理的。既然是异步监听,就涉及到监听事件的发布者和订阅者,数据流其实就是由发布者生产,再由一个或多个订阅者进行消费的元素(item)序列。
方式9:RxJava 响应式 异步
在JDK 9 Flow 之前,响应式编程 的框架,早就存在。
比如说, 席卷了android 端编程的 RxJava 框架。RxJava 是一种响应式编程,来创建基于事件的异步操作库。
这个组件,是 Netflix的杰作,也叫作Netflix RxJava。
这个框架,在Java 后端的中间件中,也有广泛使用,比如在Hystrix 源码中,就用大量用到。
方式10:Reactor 响应式 异步
目前,在 Java 领域实现了反应式编程的技术除了 Netflix RxJava ,还有 Spring 的 Project Reactor。
Netflix 网飞 的RxJava 出现时间更早,在前端开发领域应用的比后端更要广泛一些。
Spring 的 Project Reactor的 3.0 版本作为 Spring 5 的基础,在17年底发布,推动了后端领域反应式编程的发展。
方式11:Spring的@Async异步
在Spring中,使用@Async标注某方法,可以使该方法变成异步方法,这些方法在被调用的时候,将会在独立的线程中进行执行,调用者不需等待该方法执行完成。
但在Spring中使用@Async注解,需要使用@EnableAsync来开启异步调用。
@Async注解,默认使用系统自定义线程池。
在实际项目中,推荐等方式是是使用自定义线程池的模式。
可在项目中设置多个线程池,在异步调用的时候,指明需要调用的线程池名称,比如:@Async(“taskName”)
方式12:EventBus 发布订阅模式异步
实际开发中,常常 通过事件总线EventBus/AsyncEventBus进行JAVA模块解耦 ,
比如,在顶级开源组件 JD hotkey的源码中, 就多次用到 EventBus/AsyncEventBus进行JAVA模块解耦
掌握了 EventBus ,在平时的开发中,多了一个神器
方法13:Spring ApplicationEvent事件实现异步
Spring内置了简便的事件机制,原理和EventBus 差不多
通过Spring ApplicationEvent事件, 可以非常方便的实现事件驱动,核心类包括
ApplicationEvent,具体事件内容,事件抽象基类,可继承该类自定义具体事件
ApplicationEventPublisher,事件发布器,可以发布ApplicationEvent,也可以发布普通的Object对象
ApplicationListener,事件监听器,可以使用注解@EventListener
TransactionalEventListener,事务事件监听,可监听事务提交前、提交后、事务回滚、事务完成(成功或失败)
方法14: RocketMq 消息队列 分布式 发布订阅模式 异步
上游系统对下游系统的调用若为同步调用,则会大大降低系统的吞吐量与并发度,且系统耦合度太高。
而异步调用则会解决这些问题。
所以两层之间若要实现由同步到异步的转化,一般性做法就是,在这两层间添加一个MQ层。
方法15:Redis 消息队列 分布式 发布订阅模式 异步
在springboot项目中,一般分布式 发布订阅模式 异步,都是用RocketMQ的方式,
如果集成太麻烦了,而一般系统里面已经有了redis,就用了redis做异步的功能
Redis发布订阅(pub/sub)是一种消息通信模式:
发送者(pub)发送消息,
订阅者(sub)接收消息。
Redis 发布订阅(pub/sub)实现了消息系统,发送者(在redis术语中称为发布者)在接收者(订阅者)接收消息时发送消息。传送消息的链路称为信道。
方法16: Distruptor 框架异步
Disruptor是一个优秀的并发框架,使用无锁编程+环形队列架构, 是高性能异步队列的王者组件
Disruptor 可以使用在 非常多 的生产者 消费者 异步场景
方法17:ForkJoin 框架异步
Fork/Join框架是JDK1.7提供的一个并行任务执行框架,它可以把一个大任务分成多个可并行执行的子任务,然后合并每个子任务的结果,得到的大任务的结果。
有点类似Hadoop的MapReduce,Fork/Join框架也可以分成两个核心操作:
Fork操作:将大任务分割成若干个可以并行执行的子任务
Join操作:合并子任务的执行结果
方法18:RocketMQ源码中ServiceThread 能急能缓的高性能异步
RocketMQ源码中, 实现了一种特殊的,高性能异步: 能急能缓 ServiceThread 异步。
能急能缓 ServiceThread 异步 有两个特点:
既能周期性的执行异步任务
还能紧急的时候,执行应急性的任务
RocketMQ的吞吐量达到 70Wqps,ServiceThread 的异步框架,发挥了重要的价值。
尼恩 在 实现《10WQps推送中台实操》使用了 ServiceThread 异步框架,在 实现《100WQps三级缓存组件实操》,实操时,也使用了 ServiceThread 异步框架。
总之, ServiceThread 异步框架是RocketMq的精华之一,性能非常高,也 非常好用。
方式19:Kotlin 协程 异步
2018 年,一种新的 JVM 编程语言开始流行:Kotlin。
Kotlin 首先流行在 Android 开发领域,因为它得到了 Google 的首肯和支持。
但对于后端开发领域,因为一项特性,使得 Kotlin 也非常值得注意。那就是 Kotlin Coroutine(后文称 Kotlin 协程)。
协程技术不是什么新技术,它在很多语言中都有实现,比如大家所熟悉的 Python、Lua、Go 都是支持协程的。
在不同语言中,协程的实现方法各有不同。因为 Kotlin 的运行依赖于 JVM,不能对 JVM 进行修改,因此,Kotlin 不能在底层支持协程。
同时,Kotlin 是一门编程语言,需要在语言层面支持协程,而不是像框架那样在语言层面之上支持。
因此,Kotlin 对协程支持最核心的部分是在编译器中。因为对这部分原理的解释在之前文章中都有涉及,因此不在这里重复。
使用 Kotlin 协程之后最大的好处是异步代码的可读性大大提高。