精华内容
下载资源
问答
  • 响应式编程

    2017-08-02 10:31:25
    响应式编程
  • 响应式编程响应式编程是一种异步编程范式,它关注数据流和变化的传播。这意味着可以通过使用编程语言轻松地表示静态(例如数组)和动态(例如事件发射器)数据流。Reactive Streams为基于JVM的响应库提供了规范,它定义...

    响应式编程

    响应式编程是一种异步编程范式,它关注数据流和变化的传播。这意味着可以通过使用编程语言轻松地表示静态(例如数组)和动态(例如事件发射器)数据流。

    Reactive Streams为基于JVM的响应库提供了规范,它定义了一组接口和交互规则。在Java 9中,这些接口已经集成到java.util.concurrent.Flow类之下。

    在面向对象编程语言中,响应式编程通常以观察者模式的扩展呈现。还可以将响应式流模式和迭代器模式比较,一个主要的区别是,迭代器基于”拉“,而响应式流基于”推“。

    使用迭代器是一种命令式编程,由开发者决定何时去访问序列中的next()元素。而在响应式流中,与Iterable-Iterator对应的是Publisher-Subscriber。当新的可用元素出现时,发布者通知订阅者,这种”推“正是响应的关键。此外,应用于推入元素上的操作是声明式的而不是命令式的:程序员要做的是表达计算的逻辑,而不是描述精准的控制流程。

    除了推送元素,响应式编程还定义了良好的错误处理和完成通知方式。发布者可以通过调用next()方法推送新的元素给订阅者,也可以通过调用onError()方法发送一个错误信号或者调用onComplete()发送一个完成信号。错误信号和完成信号都会终止序列。

    响应式编程非常灵活,它支持没有值、一个值或n个值的用例(包括无限序列,例如时钟的连续滴答声)。

    但是让我们首先考虑,为什么我们需要响应式编程?

    阻塞可能是浪费的

    现代的应用需要满足大量的用户并发访问,尽管硬件的能力依然在不断提高,软件的性能仍然是一个关键问题。

    通常有两种方法可以提升程序的性能:

    并行化:使用更多的线程和更多的硬件资源。

    在如何使用现有资源方面寻求更高的效率。

    通常,Java开发者使用阻塞代码编程。在出现性能瓶颈之前,这种做法没有问题,此时就需要引入额外的线程,来运行相似的阻塞代码。但是,这种资源利用率的扩展可能很快引来争用和并发问题。

    更糟糕的是,阻塞会浪费资源。如果仔细观察,只要程序涉及一些延迟(特别是IO,比如数据库请求或网络调用),资源就会被浪费,因为一个(或多个线程)正处于空闲状态,等待数据。

    所以,并行化方法并不是什么灵丹妙药。为了提高硬件资源的利用率,响应式编程是必要的。但是它也很复杂,因为容易造成资源浪费。

    使用异步来解决?

    上文提到的第二种方法是寻求更高的效率,可以解决资源浪费问题。通过编写异步非阻塞代码,你可以将执行切换到另一个使用相同底层资源的活动任务上,在异步执行完成后返回到当前程序。

    如何在JVM上编写异步代码呢?Java提供了两种异步编程模型:

    Callbacks:异步方法没有返回值,但是提供一个额外的回调参数(一个lambda或者匿名类对象),当结果可用时调用该参数。

    Futures:异步方法立即返回一个Future对象。异步线程计算一个T值,Future对象封装对它的访问。该值不是立即可用的,但可以轮询Future对象,直到该值可用为止。

    这些技术足够好吗?并不是每个场景都适用,而且两种方式都有限制。

    回调是很难组合在一起的,很快就会导致难以阅读和维护的代码(称为”回调地狱”)。

    Futures比回调稍微好一点,但是在组合方面依然做的不够好,即使Java 8中引入了CompletableFuture。把多个Future编排到一起虽然可行,但是并不容易。而且,Future还有另外的问题:通过调用get()方法,很容易让Future对象进入到另一种阻塞情景;它们不支持延时计算;不支持多值和高级错误处理。

    从命令式编程到响应式编程

    响应式编程库Reactor旨在解决JVM上这些”经典“的异步编程方式的缺点,同时关注几个额外的方面:

    可组合性和可读性;

    使用丰富的操作符词汇操作数据流;

    在订阅数据流之前什么也不会发生(延时计算);

    背压(backpressure)或消费者向生产者发送发射速率过快的信号的能力;

    与并发无关的高级抽象;

    可组合性和可读性

    可组合性指的是编排、协调多个异步任务的能力。包括使用先前任务的结果为后续任务提供输入,或者以fork-join格式执行多个任务以及在更高级别的系统中将异步任务作为独立组件重用。

    编排任务的能力与代码的可读性和可维护性是紧密相关的。随着异步处理的数量和复杂性的增加,编写和阅读代码变得越来越困难。正如我们所看到的,回调模式很简单,但是它的一个主要缺点是,对于复杂的任务,你需要从回调中执行回调,回调本身嵌套在另一个回调中,等等。这种混乱被称为回调地狱。可以想象,这样的代码很难回头进行推理。

    装配线类比

    你可以将在响应式应用中处理数据想象成(数据)在装配线上移动。响应式编程既是传送带,又是工作站。原材料从一个数据源(最初的Publisher)中倾泻而出,最终成为准备推送给消费者(Subscriber)的成品。

    原材料可以经过各种转换和中间步骤,或者成为将中间部件组装在一起的大型装配线的一部分。如果在某一点发生了小故障或者阻塞,受影响的工作站可以向上游发信号限制原材料的流动。

    操作符

    操作符就是装配线中的工作站。每一个操作符都向发布者(Publisher)添加新的行为,并将上一步中的发布者包装到一个新的实例中。整个链条就是这样连接在一起的,数据源自第一个发布者(Publisher),并沿着链条向下移动,在每个链接处被转换。最终,一个订阅者(Subscriber)结束这个流程。

    订阅前什么也不会发生

    在Reactor中,当你编写一个发布者(Publiser)链时,默认情况下不会开始向其中注入数据。相反,你创建的是一个异步处理的抽象描述(这有助于重用性和组合)。

    通过订阅操作(Subscribe()),你将发布者绑定到一个订阅者,从而触发整个链中的数据流。这是通过订阅者(Subscriber)发出一个请求(request())信号在内部实现的,该信号向上游传播,一直到源头发布者(Publisher)。

    背压

    向上游传播信号也被用来实现背压。在装配线类比中,我们将其描述为当工作站处理速度比上游工作站慢时,沿装配线向上游发送的反馈信号。

    响应式流规范中定义的真正的机制与装配线类比非常相似:订阅者可以在无限制的模式下工作,并让数据源以最快的速度推送数据;或者它可以用request()机制向数据源发出信号,表明自己最多可以处理n个数据。

    中间操作符还可以在传输过程中更改请求。假设有一个buffer操作符以10个元素为一个批次对元素进行分组。如果订阅者请求1个缓冲区,数据源就要生成10个元素。一些操作符还实现预取(prefetching)策略,这避免了每次请求一个元素的往返开销,如果在被请求之前生成元素的成本不是太高,那么这样做是有益的。

    预取策略将推模型转换为推-拉混合模型,在这种混合模型中,下游可以从上游拉出n个元素(如果它们已经可用)。但是,如果元素还没有准备好,它们会在生产好之后由上游推给下游。

    热响应式序列和冷响应式序列

    在响应式编程库的Rx家族中,我们可以区分两大类响应式序列:热响应式序列和冷响应式序列 。这种区别主要与响应式流如何对订阅者做出响应有关:

    冷响应式序列对每个订阅者(包括数据源)会重新启动一个全新的响应序列。

    热响应式序列对每个订阅者不会重零开始。相反,后来的订阅者只能接收到他们订阅后发出的数据。但是,请注意,一些热响应式序列可以缓存或回放全部或部分数据发布历史。从一般的角度来看,热序列甚至可以在没有订阅者在监听时发射数据(“订阅前什么都不会发生”规则的一个例外)。

    展开全文
  • (1)什么是响应式编程——响应式Spring的道法术器

    万次阅读 多人点赞 2018-03-06 10:54:22
    响应式编程之道 1.1 什么是响应式编程? 在开始讨论响应式编程(Reactive Programming)之前,先来看一个我们经常使用的一款堪称“响应式典范”的强大的生产力工具——电子表格。 举个简单的例子,某电商网站...

    本系列其他文章见:《响应式Spring的道法术器》

    响应式编程之道

    1.1 什么是响应式编程?

    在开始讨论响应式编程(Reactive Programming)之前,先来看一个我们经常使用的一款堪称“响应式典范”的强大的生产力工具——电子表格。

    举个简单的例子,某电商网站正在搞促销活动,任何单品都可以参加“满199减40”的活动,而且“满500包邮”。吃货小明有选择障碍(当然主要原因还是一个字:穷),他有个习惯,就是先在Excel上根据预算算好自己要买的东西:

    RX9VjUmQltKah38.jpg

    相信大家都用过Excel中的公式,这是一个统计购物车商品和订单应付金额的表格,其中涉及到一些公式:

    q4p6hzMXQfYPurA.jpg

    上图中蓝色的线是公式的引用关系,从中可以看出,“商品金额”是通过“单价x数量”得到的,“满199减40”会判断该商品金额是否满199并根据情况减掉40,右侧“订单总金额”是“满199减40”这一列的和,“邮费”会根据订单总金额计算,“最终应付款”就是订单总金额加上邮费。

    1.1.1 变化传递(propagation of change)

    为什么说电子表格软件是“响应式典范”呢,因为“单价”和“数量”的任何变动,都会被引用(“监听”)它的单元格实时更新计算结果,如果还有图表或数据透视图引用了这块数据,那么也会相应变化,做到了实时响应。变化的时候甚至还有动画效果,用户体验一级棒!

    这是响应式的核心特点之一:变化传递(propagation of change)。一个单元格变化之后,会像多米诺骨牌一样,导致直接和间接引用它的其他单元格均发生相应变化。

    Ex2SfmLoNg7TOuw.jpg

    看到这里,你可能会说,“切~ 不就是算付款金额吗,购物网站上都有这个最基础不过的功能啊”,这就“响应式”啦?但凡一个与用户交互的系统都得“响应”用户交互啊

    但是在响应式编程中,基于“变化传递”的特点,触发响应的主体发生了变化。假设购物车管理和订单付款是两个不同的模块,或者至少是两个不同的类——CartInvoice。也许我们的代码是这样的:

    Product.java(假设商品有两个属性nameprice,简单起见,price就不用BigDecimal类型了)

    public class Product {
        private String name;
        private double price;
        // 构造方法、getters、setters
    }
    

    Cart模块中:

    import com.example.Invoice; // 2
    
    public class Cart {
        ...
        public boolean addProduct(Product product, int quantity) {
            ...
            double figure = product.getPrice() * quantity;
            invoice.update(figure); // 1
            ...
        }
        ...
    }
    
    1. 是由Cart的对象去调用Invoice对象的更新订单金额的方法;
    2. Cart的代码中需要import Invoice

    而我们再观察这个Excel,发现“订单总金额”的计算公式不仅位于自己的单元格中,而且这个公式能主动监听和响应购物车数据的变化事件。对于购物车来说,它没有对订单付款方面的任何公式引用。感觉就像这样:

    假设数据流有操作的商品product和变化个数quantity两个属性:

    public class CartEvent {
        private Product product;
        private int quantity;
        // 构造方法、getters、setters
    }
    

    Invoice模块中:

    import com.example.Cart // 2
    
    public class Invoice {
        ...
        public Invoice(Cart cart) {
            ...
            this.listenOn(cart);    // 1
            ...
        }
        // 回调方法
        public void onCartChange(CartEvent event) {
            ...
        }
        ...
    }
    
    1. 是由Invoice的对象在初始化的时候就声明了对Cart对象的监听,从而一旦Cart对象有响应的事件(比如添加商品)发生的时候,Invoice就会响应;
    2. Invoice的代码中import Cart

    做过Java桌面开发的朋友可能会想到Java swing中的各种监听器,比如MouseListener能够监听鼠标的操作,并实时做出响应。所以C/S的客户端总是比B/S的Web界面更具有响应性嘛。

    所以,这里我们说的是一种生产者只负责生成并发出数据/事件,消费者来监听并负责定义如何处理数据/事件的变化传递方式

    那么,Cart对象如何在发生变化的时候“发出”数据或事件呢?

    1.1.2 数据流(data stream)

    这些数据/事件在响应式编程里会以数据流的形式发出。

    我们再观察一下购物车,这里有若干商品,小明每次往购物车里添加或移除一种商品,或调整商品的购买数量,这种事件都会像过电一样流过这由公式串起来的多米诺骨牌一次。这一次一次的操作事件连起来就是一串数据流(data stream),如果我们能够及时对数据流的每一个事件做出响应,会有效提高系统的响应水平。这是响应式的另一个核心特点:基于数据流(data stream)

    如下图是小明选购商品的过程,为了既不超预算,又能省邮费,有时加有时减:

    这一次一次的操作就构成了一串数据流。Invoice模块中的代码可能是这样:

        public Invoice(Cart cart) {
            ...
            this.listenOn(cart.eventStream());  // 1
            ...
        }
    
    1. 其中,cart.eventStream()是要监听的购物车的操作事件数据流,listenOn方法能够对数据流中到来的元素依次进行处理。

    1.1.3 声明式(declarative)

    我们再到listenOn方法去看一下:

    Invoice模块中,上边的一串公式被组装成如下的伪代码:

        public void listenOn(DataStream<CartEvent> cartEventStream) {   // 1
            double sum = 0;
            double total = cartEventStream
                // 分别计算商品金额
                .map(cartEvent -> cartEvent.getProduct().getPrice() * cartEvent.getQuantity())  // 2
                // 计算满减后的商品金额
                .map(v -> (v > 199) ? (v - 40) : v)
                // 将金额的变化累加到sum
                .map(v -> {sum += v; return sum;})
                // 根据sum判断是否免邮,得到最终总付款金额
                .map(sum -> (sum > 500) ? sum : (sum + 50));
            ...
    
    1. cartEventStream是数据流,DataStream是某种数据流类型,可以暂时想象成类似在Java 8版本增加的对数据流进行处理的Stream API(下节会说到为啥不用Java Stream)。

    2. map方法用于对数据流中的元素进行映射,比如第一个将cartEvent中的商品价格和数量拿到,然后算出本次操作的金额;第二个判断是否能享受“满199减40”的活动。

    这里的伪代码用到了lambda,它非常适用于数据流的处理。没有接触过lambda的话没有关系,我们后续会再聊到它。

    这是一种**“声明式(declarative)”**的编程范式。通过四个串起来的map调用,我们先声明好了对于数据流“将会”进行什么样的处理,当有数据流过来时,就会按照声明好的处理流程逐个进行处理。

    比如对于第一个map操作:

    olW5mzADdxHt6nS.jpg

    **声明式编程范式的威力在于以不变应万变。**无论到来的元素是什么,计算逻辑是不变的,从而形成了一种对计算逻辑的“绑定”。

    再举个简单的例子方便理解:

    a = 1;
    b = a + 1;
    a = 2;
    

    这个时候,b是多少呢?在Java以及多数语言中,b的结果是2,第二次对a的赋值并不会影响b的值。

    假设Java引入了一种新的赋值方式:=,表示一种对a的绑定关系,如

    a = 1;
    b := a + 1;
    a = 2;
    

    由于b保存的不是某次计算的值,而是针对a的一种绑定关系,所以b能够随时根据a的值的变化而变化,这时候b==3,我们就可以说:=是一种声明式赋值方式。而普通的=是一种命令式赋值方式。事实上,我们绝大多数的开发都是命令式的,如果需要用命令式编程表达类似上边的这种绑定关系,在每次a发生变化并需要拿到b的时候都得执行b = a + 1来更新b的值。

    如此想来,“绑定美元政策”不也是一种声明式的范式吗~

    总结来说,命令式是面向过程的,声明式是面向结构的

    不过命令式和声明式本身并无高低之分,只是声明式比较适合基于流的处理方式。这是响应式的第三个核心特点:声明式(declarative)。结合“变化传递”的特点,声明式能够让基于数据流的开发更加友好。

    1.1.4 总结

    总结起来,响应式编程(reactive programming)是一种基于数据流(data stream)和变化传递(propagation of change)的声明式(declarative)的编程范式。

    响应式编程的“变化传递”就相当于果汁流水线的管道;在入口放进橙子,出来的就是橙汁;放西瓜,出来的就是西瓜汁,橙子和西瓜、以及机器中的果肉果汁以及残渣等,都是流动的“数据流”;管道的图纸是用“声明式”的语言表示的。

    这种编程范式如何让Web应用更加“reactive”呢?

    我们设想这样一种场景,我们从底层数据库驱动,经过持久层、服务层、MVC层中的model,到用户的前端界面的元素,全部都采用声明式的编程范式,从而搭建一条能够传递变化的管道,这样我们只要更新一下数据库中的数据,用户的界面上就相应的发生变化,岂不美哉?尤其重要的是,一处发生变化,我们不需要各种命令式的调用来传递这种变化,而是由搭建好的“流水线”自动传递。

    这种场景用在哪呢?比如一个日志监控系统,我们的前端页面将不再需要通过“命令式”的轮询的方式不断向服务器请求数据然后进行更新,而是在建立好通道之后,数据流从系统源源不断流向页面,从而展现实时的指标变化曲线;再比如一个社交平台,朋友的动态、点赞和留言不是手动刷出来的,而是当后台数据变化的时候自动体现到界面上的。

    具体如何来实现呢,请看下一节关于响应式流的介绍。

    展开全文
  • 文章目录历史定义设计思想迭代器模式观察者模式Reactive Streams 规范依赖核心接口接口交互流程响应式编程阻塞带来的性能浪费传统异步编程带来的缺点从命令式过渡到响应式编程参考 历史 响应式编程(Reactive ...

    历史

    响应式编程(Reactive Programming)概念最早于上世纪九十年代被提出,微软为 .NET 生态开发了 Reactive Extensions (Rx) 库用于支持响应式编程,后来 Netflix 开发了 RxJava,为 JVM 生态实现了响应式编程。随着时间的推移,2015 年 Reactive Stream(响应式流)规范诞生,为 JVM 上的响应式编程定义了一组接口和交互规则。RxJava 从 RxJava 2 开始实现 Reactive Stream 规范。同时 MongoDB、Reactor、Slick 等也相继实现了 Reactive Stream 规范。

    Spring Framework 5 推出了响应式 Web 框架

    Java 9 引入了响应式编程的 API,将 Reactive Stream 规范定义的四个接口集成到了 java.util.concurrent.Flow 类中。Java 9 提供了 SubmissionPublisher 和 `ConsumerSubscriber 两个默认实现。

    Java 8 引入了 Stream 用于流的操作,Java 9 引入的 Flow 也是数据流的操作。相比之下,Stream 更侧重于流的过滤、映射、整合、收集,使用的是 PULL 模式。而 Flow/RxJava/Reactor 更侧重于流的产生与消费,使用的是 PUSH 模式 。

    +--------------------------+     +-------------+     +------------------+     +-------------------------------+
    | Reactive Extensions (Rx) |     | RxJava 1.x  |     | Reactive Streams |     | RxJava 2                      |
    | by Microsoft             +-----> by Netflix  +-----> Specification    +-----> (Supporting Reactive Streams) |
    | for .NET                 |     | for Java 6+ |     | for JVM          |     | for Java 6+                   |
    +--------------------------+     +-------------+     +------------------+     +-------------------------------+
                                                                                                  |
                                                                                                  |
                                                                                                  |
                         +-----------------------+     +--------------------+     +---------------v---------------+
                         | Java 9 Standard       |     | Spring Framework 5 |     | Project Reactor              |
                         | (JEP-266 by Doug Lea) <-----+ Reactive Stack     <-----+ (Supporting Reactive Streams) |
                         |                       |     |                    |     | for Java 8+                   |
                         +-----------------------+     +--------------------+     +-------------------------------+
    

    定义

    响应式编程(Reactive Programing)是一种基于数据流(data stream)和变化传递(propagation of change)的声明式(declarative)的编程范式。

    设计思想

    响应式编程范式通常在面向对象语言中作为观察者模式的扩展出现。可以将其与大家熟知的迭代器模式作对比,主要区别在于:

    迭代器(Iterator) 响应式流(Reactive Stream)
    设计模式 迭代器模式 观察者模式
    数据方向 拉模式(PULL) 推模式(PUSH)
    获取数据 T next() onNext(T)
    处理完成 hasNext() onCompleted()
    异常处理 throws Exception onError(Exception)

    迭代器模式

    参考《Iterator API 总结

    观察者模式

    观察者模式是一种行为型设计模式,允许你定义一种订阅机制,可在对象事件发生时主动通知多个 “观察” 该对象的其它对象。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yiMB2Cbx-1612354134273)(https://qidawu.github.io/img/java/design-pattern/Observer.png)]

    在响应式流中,上述操作由 Publisher-Subscriber 负责。由 Publisher 生产新值并推送给 Subscriber,这个“推送”就是响应式的关键,亦即“变化传递(propagation of change)”。另外,应用于被推送值的操作(Operator)是“声明式”而不是“命令式”的:开发者表达的是计算逻辑,而不是描述其具体的控制流程。

    流程如下:

    onNext x 0..N [onError | onComplete]
    

    Reactive Streams 规范

    依赖

    Reactive Stream(响应式流)规范的 Maven 依赖如下:

    <!-- https://mvnrepository.com/artifact/org.reactivestreams/reactive-streams -->
    <dependency>
        <groupId>org.reactivestreams</groupId>
        <artifactId>reactive-streams</artifactId>
        <version>1.0.2</version>
    </dependency>
    

    核心接口

    整个依赖包中,仅仅定义了四个核心接口:

    • org.reactivestreams.Subscription 接口定义了连接发布者和订阅者的方法;
    • org.reactivestreams.Publisher<T> 接口定义了发布者的方法;
    • org.reactivestreams.Subscriber<T> 接口定义了订阅者的方法;
    • org.reactivestreams.Processor<T,R> 接口定义了处理器;

    org.reactivestreams

    接口交互流程

    简要交互如下:

    process_ofreactive_stream

    API 交互如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YgJAJinH-1612354134281)(https://qidawu.github.io/img/java/reactive-stream/reactive-stream/process_of_reactive_stream_2.png)]

    响应式编程

    Reactor 是一款基于 JVM 的完全非阻塞的响应式编程框架。它实现了 Reactive Streams 规范,具有高效的流量控制(以管理背压的形式),并扩展了大量特性。Reactor 直接集成了 Java 8 的函数式 API,特别是:

    • java.util.CompletableFuture
    • java.util.stream.Stream
    • java.time.Duration

    Reactor Core 提供了两个可组合式异步串行 API

    • reactor.core.publisher.Mono (for [0|1] elements)
    • reactor.core.publisher.Flux (for [N] elements)

    这两个类都是 org.reactivestreams.Publisher 接口的实现类:

    Publisher

    Reactor Core 还提供了 org.reactivestreams.Subscriber` 接口的实现类,如下(还有其它子类,此处不一一例举):

    Subscriber

    不过一般不会直接使用该实现类,而是使用 MonoFlux 提供的 subscribe 方法(如下图),并传入 Lambda 表达式语句(代码即参数),由方法的实现负责将参数封装为 Subscriber 接口的实现类,供消费使用:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RMJ4OrYd-1612354134284)(https://qidawu.github.io/img/java/reactive-stream/reactor/reactor_subscribe.png)]

    响应式编程,如 Reactor 旨在解决 JVM 上传统异步编程带来的缺点、以及编程范式上从命令式过渡到响应式编程:

    阻塞带来的性能浪费

    现代的应用程序通常有大量并发请求。即使现代的硬件性能不断提高,软件性能仍然是关键瓶颈。

    广义上讲,有两种方法可以提高程序的性能:

    • 利用并行(parallel)使用更多 CPU 线程和更多硬件资源。
    • 提升现有资源的利用率。

    通常,Java 开发者使用阻塞方式来编写程序。除非达到性能瓶颈,否则这种做法可行。之后,通过增加线程数,运行类似的阻塞代码。 但这种方式很快就会导致资源争用和并发问题

    更糟糕的是,阻塞会浪费资源。试想一下,程序一旦遇到一些延迟(特别是 I/O 操作,例如数据库请求或网络请求),就会导致资源浪费,因为大量线程处于空闲状态,等待数据,甚至导致资源耗尽。尽管使用池化技术可以提升资源利用率、避免资源耗尽,但只能缓解而不能解决根本问题,而且池内资源同样有耗尽的问题。

    因此,并行技术并非银弹。

    传统异步编程带来的缺点

    Java 提供了两种异步编程模型:

    • Callbacks: 异步方法没有返回值,但是带有一个额外的 callback 回调参数(值为 Lambda 表达式或匿名类),该参数在结果可用时被调用。
    • Futures: 异步方法调用后立即返回一个 Future<T> 对象。异步方法负责计算出结果值 T,由 Future 对象包装起来。该结果值并非立即可用,可以轮询该 Future 对象,直到结果值可用为止。例如 ExecutorService 提供的方法 <T> Future<T> submit(Callable<T> task)

    两种模型都有一个共同的缺点:难以组合代码,从而导致代码可读性差、难以维护。例如,当业务逻辑复杂,步骤存在依赖关系时,会导致回调嵌套过深,从而导致著名的 Callback Hell 问题。

    详见代码例子。

    从命令式过渡到响应式编程

    from_imperative_to_reactive_programming

    参考

    《Reactive Java Programming》

    Reactive Streams 规范

    https://en.wikipedia.org/wiki/Reactive_Streams

    http://openjdk.java.net/jeps/266

    Reactor 框架,实现 Reactive Streams 规范,并扩展大量特性

    http://reactivex.io/

    • 《Reactive Programming with RxJava》

    https://spring.io/reactive

    Web on Reactive Stack - Spring

    • 基于 Reactor 框架实现
    • 默认基于 Netty 作为应用服务器
    • 好处:能够以固定的线程来处理高并发(充分发挥机器的性能)
    • 提供 API:
      • Spring WebFlux
      • WebClient
      • WebSockets
      • Testing
      • RSocket
      • Reactive Libraries
    展开全文
  • 响应式编程:理解响应式编程

    千次阅读 2016-02-23 14:40:26
    了解响应式编程

    引言


    响应式编程并不是一个新概念。早在90年代末,微软的一名计算机科学家就提出了响应式编程。用来设计和开发微软的某些库。

    定义


    响应式编程(Reactive Programming,RP)的定义有很多个版本,如wikistackoverflow还有ReactiveX。但是每个版本的中心思想都是:响应式编程(RP)是一种基于异步数据流概念的编程范式。而RP的精髓也在于对数据流(Dataflow, Event)的高效处理。
    而Rx,及Reactive Extensions,是对微软的.NET的一个响应式扩展。国外的一些大牛,对Rx也有一些自己的理解,如:The introduction to Reactive Programming you’ve been missing ,及其译文版

    RxJava


    Rx,及Reactive Extensions。Rx是一种编程模型。Rx借助可观察的序列来提供一种简单的方式来创建异步的,基于事件驱动的程序。
    2013年,Netflix第一次向世界展示了RxJava。 RxJava的主要特点有:

    • 易于并发从而更好的利用服务器的能力
    • 易于有条件的异步执行
    • 一种避免回调地狱的更好方式
    • 一种响应式方法

    RxJava Observable类源自于Gang of Four的观察者模式。但有高于观察者模式:

    • 生产者在没有数据产生时发出通知:onComplete()
    • 生产者发生错误时发出通知:onError()
    • RxJava的Observable能够组合而不是嵌套,从而避免回调地狱

    Rx编程思想


    在传统的命令式编程中,如表达式a=b+c。即把b的值和c的值相加之后,赋值给a之后,不管b和c如果变化都不会影响a;但是,在响应式的世界里,a的值会随着b的值或c的值变化而变化。
    亦即,响应式可以将相关模型的变化直接反应到视图上。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,426
精华内容 2,570
关键字:

响应式编程