一文读懂响应式编程到底是什么?


一文读懂响应式编程到底是什么?文章插图
本文作者知秋 , 节选自《Java编程方法论:响应式Spring Reactor 3设计与实现》一书 。
-------
最近几年 , 随着Go、Node 等新语言、新技术的出现 , Java 作为服务器端开发语言老大的地位受到了不小的挑战 。 虽然Java 的市场地位在短时间内并不会发生改变 , 但Java 社区还是将挑战视为机遇 , 并努力、不断地提高自身应对高并发服务器端开发场景的能力 。
?为了应对高并发服务器端开发场景 , 在2009 年 , 微软提出了一个更优雅地实现异步编程的方式—— Reactive Programming, 我们称之为响应式编程 。
随后 , 各语言很快跟进 , 都拥有了属于自己的响应式编程实现 。 比如 , JavaScript 语言就在ES6 中通过Promise 机制引入了类似的异步编程方式 。 同时 , Java 社区也在快速发展 , Netflix 和LightBend 公司提供了RxJava 和Akka Stream 等技术 , 使得Java 平台也有了能够实现响应式编程的框架 。
当下 , 我们通过Mina 和Netty 这样的NIO 框架其实就能完成高并发下的服务器端开发任务 , 但这样的技术只掌握在少数高级开发人员手中 , 因为它们难度较大 , 并不适合大部分普通开发者 。
虽然目前已经有不少公司在实践响应式编程 , 但整体来说 , 其应用范围依旧不大 。 出现这种情况的原因在于当下缺少简单、易用的技术 , 这些技术需要能使响应式编程更加普及 , 并做到如同Spring MVC 一样结合Spring 提供的服务对各种技术进行整合 。
在2017 年9 月28 日 , Spring 5 正式发布 。 Spring 5 发布最大的意义在于 , 它将响应式编程技术的普及向前推进了一大步 。 而同时 , 作为在背后支持Spring 5 响应式编程的框架Spring Reactor , 也进入了里程碑式的3.1.0 版本 。
响应式编程到底是什么?
在现实生活中 , 当我们听到有人喊我们名字的时候 , 会对其进行响应 , 也就是说 , 我们是基于事件驱动模式来进行编程的 。 所以这个过程其实就是下发产生的事件 , 然后我们作为消费者对下发事件进行一系列的消费 。
从这个角度来说 , 对整个代码的设计应该是针对消费者来进行的 。 比如 , 看电影 , 有些画面我们不想看 , 那就闭上眼睛;有些声音不想听 , 那就捂上耳朵 。 其实这就是对消费者的增强包装 , 我们把复杂的逻辑拆分开 , 然后将其分割成一个个小任务进行封装 , 于是就有了诸如?lter、map、skip、limit 等操作 。
01
并发与并行的关系
可以说 , 并发很好地利用了CPU 时间片的特性 , 也就是操作系统选择并运行一个任务 , 接着在下一个时间片内运行另一个任务 , 并把前一个任务设置成等待状态 。 其实并发并不意味着并行 。
具体列举下面几种情况 。
① 有时候 , 多线程执行会提高应用程序的性能 , 而有时候反而会降低应用程序的性能 。 这在 JDK 中Stream API 的使用上体现得很明显 , 如果任务量很小 , 而我们又使用了并行流 , 反而降低了应用程序的性能 。
② 在多线程编程中 , 可能会同时开启或者关闭多个线程 , 这样会产生很大的性能开销 ,也降低了应用程序的性能 。
③ 当线程同时处于等待I/O 的过程中时 , 并发可能会阻塞CPU 资源 , 其后果不仅是用户长时间等待 , 而且会浪费CPU 的计算资源 。
④ 如果几个线程共享了一个数据 , 情况就会变得有些复杂 。 我们需要考虑数据在各个线程中的状态是否一致 。 为了达到数据一致的目的 , 很可能会使用synchronized 或者lock 相关操作 。
现在 , 你对并发有一定的了解了吧 。 并发很好 , 但并不一定会实现并行 。 并行是在多核CPU 上同一时间运行多个任务或者一个任务分为多块同时执行(如ForkJoin) 。 单核CPU 的话 , 就不要考虑并行了 。
补充一点 , 实际上多线程就意味着并发 , 但是并行只发生在这些线程在同一时间调度、分配到不同CPU 上执行的情况下 。 也就是说 , 并行是并发的一种特定形式 。 一个任务里往往会产生很多元素 , 这些元素在不参与操作的情况下大都只能处于当前线程中 , 这时我们可以对其进行ForkJoin , 但这对很多程序员来讲有时候很不好操作、控制 , 上手难度有些大 。 这时如果用响应式编程 , 就可以简单地通过所提供的调度API 轻松做到事件元素的下发、分配 , 其内部会将每个元素包装成一个任务并提交到线程池中 , 我们可以根据任务是计算型的还是I/O 型的来选择相应的线程池 。
在这里 , 需要强调一下 , 线程只是一个对象 , 不要把它想象成CPU 中的某一个执行核心 , 这是很多人都在犯的错 , CPU 时间片会切换执行这些线程 。