Java中的响应式编程浅析

最近接触到响应式编程的概念,简单了解了一下在java中的响应式编程。响应式编程是一个专注于数据流和变化传递的异步编程范式。

响应式编程是一种编程概念,在很多编程语言中都有应用。其中,在Java中,我们比较熟悉的有RxJava,有关RxJava的介绍,已经有大神写出比较完善的介绍:
深入浅出RxJava(一:基础篇)
深入浅出RxJava(二:操作符)
深入浅出RxJava三–响应式的好处

以上三篇博客已经初步介绍RxJava的使用方式以及一些应用场景,在此不再多介绍如何使用,这里尝试简单分析一下他的设计原理以及实现方式。

响应式编程是观察者模式的扩展,RxJava中的实现也是如此。下面是一个观察者模式demo。

public class ReactiveDemo {

    public static void main(String[] args){
        //可观察对象
        MyObservable observable = new MyObservable();
        //添加观察者
        observable.addObserver((o, arg) -> {
            Util.println("观察者1处理事件:" + arg.toString());
        });

        observable.addObserver((o, arg) -> {
            Util.println("观察者2处理事件:" + arg.toString());
        });

        observable.addObserver((o, arg) -> {
            Util.println("观察者3处理事件:" + arg.toString());
        });
        //发布事件通知观察者
        observable.setChanged();
        observable.notifyObservers("事件@@");
    }


    static class MyObservable extends Observable{
        @Override
        public void setChanged(){
            super.setChanged();
        }
    }
}

执行输出

2018-06-23 18:16:43:993[main] 观察者3处理事件:事件@@
2018-06-23 18:16:44:015[main] 观察者2处理事件:事件@@
2018-06-23 18:16:44:015[main] 观察者1处理事件:事件@@

从输出可以看出,代码的执行顺序并非按照我们的代码顺序执行,而是反过来,通过debug可以进一步看到,在添加观察者回调函数时,回调函数代码没有执行(这不是废话嘛0.0),知道被观察者发布事件通知观察者处理事件时才执行回调函数,并且都是在main线程中同步执行。这种方式可以称为同步非阻塞的响应式编程。既然有同步式的非阻塞,那就有异步非阻塞的响应式编程,在Java中的Swing就是一个很好的例子。
下面看一下我们最常见的一个swing例子。

public class SwingFrame {
    public static void main(String[] args) {
        JFrame jFrame = new JFrame();
        jFrame.setVisible(true);
        jFrame.setBounds(200,200,400,400);

        jFrame.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                CommonUtil.println("鼠标点击事件");
            }
        });

        jFrame.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                super.focusGained(e);
                CommonUtil.println("焦点事件");
            }
        });
    }
}

执行程序,看看鼠标点击事件和焦点事件。

2018-06-24 07:53:20:753[AWT-EventQueue-0] 焦点事件
2018-06-24 07:53:25:216[AWT-EventQueue-0] 鼠标点击事件
2018-06-24 07:53:27:849[AWT-EventQueue-0] 焦点事件
2018-06-24 07:53:30:416[AWT-EventQueue-0] 鼠标点击事件

尝试了几次,发现在swing的事件响应处理并不是在main线程里面进行处理的,鼠标点击和焦点事件处理都是在一个叫AWT-EventQueue-0的线程中进行处理,可以看见这种异步处理的方式有别于上面的观察者模式。这里对于事件的响应更加类似于一种对事件进行拉取的方式,我们点击窗体,发现打印鼠标事件是有延迟的,原因就是这里对于事件的获取是采用另起一个线程轮询策略,监听到对应的事件之后委托给对应的事件处理器(回调函数)进行处理,这种方式叫异步非阻塞。

基于以上概念延伸出来的Reactive web,实现思路大致类似。我们了解一下原生servlet的Reactive web是怎么实现的。下面是一段网上找的描述。

在服务器的并发请求数量比较大的时候,会产生很多的servlet线程(这些servlet线程在servlet容器的线程池中维护),如果每个请求需要耗费的时间比较长(比如,执行了一些IO的处理等),在之前的非异步的servlet中,这些servlet线程将会阻塞,严重耗费服务器的资源.而在servlet3.0中首次出现的异步servlet,通过一个单独的新的线程来执行这些比较耗时的任务(也可以把这些任务放到一个自己维护的线程池里),servlet线程立即返回servlet容器的servlet池以便响应其他请求,这样,在降低了系统的资源消耗的同时,也会提升系统的吞吐量
这里写图片描述

上面这种异步处理请求的方式是我们开发中常用的思路,在netty中的selector,worker概念和这个类似,下面看一下servlet中如何实现。

@WebServlet(urlPatterns = "/asyncServlet", asyncSupported = true)
public class AsyncServlet extends HttpServlet{
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response) throws ServletException, IOException {
        CommonUtil.println("开始执行servlet");
        //开启异步上下文
        AsyncContext asyncContext = request.startAsync();
        //异步上下文设置回调函数(监听器)
        asyncContext.addListener(new AsyncListener() {
            @Override
            public void onComplete(AsyncEvent asyncEvent) throws IOException {
                ServletResponse response1 = asyncEvent.getSuppliedResponse();
                response1.setContentType("text/html;charset=UTF-8");
                response1.getWriter().println("complete回调函数返回输出");
                CommonUtil.println("complete回调函数完成");
            }

            @Override
            public void onTimeout(AsyncEvent asyncEvent) throws IOException {

            }

            @Override
            public void onError(AsyncEvent asyncEvent) throws IOException {

            }

            @Override
            public void onStartAsync(AsyncEvent asyncEvent) throws IOException {

            }
        });
        //开启新的工作线程,释放servlet处理请求线程,工作完后回调异步上下文的监听器
        new Thread(() -> {
            try {
                ServletResponse response1 = asyncContext.getResponse();
                response1.setContentType("text/html;charset=UTF-8");
                response1.getWriter().print("异步工作线程返回输出");
                //出发回调函数onComplete()
                CommonUtil.println("工作线程完成");
                asyncContext.complete();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        CommonUtil.println("释放servlet线程");
    }
}

执行程序并且访问,输出

2018-06-24 09:37:18:581[http-nio-8080-exec-1] 开始执行servlet
2018-06-24 09:37:18:584[http-nio-8080-exec-1] 释放servlet线程
2018-06-24 09:37:18:585[Thread-13] 工作线程完成
2018-06-24 09:37:18:587[http-nio-8080-exec-2] complete回调函数完成

明显可以看到接收请求线程,工作线程和回调函数线程都是不同的线程,这里面的回调方式,我猜测跟swing的事件处理方式可能一样,有专门线程进行轮询(没验证。。)。上面代码可以看出实现思路是开辟一个工作线程处理io等,然后触发上下文监听器回调函数,基本思路是这样实现。

这里的处理方式的好处是, 正如上面所说的,”servlet线程立即返回servlet容器的servlet池以便响应其他请求,这样,在降低了系统的资源消耗的同时,也会提升系统的吞吐量”。

ractive的编程方式,不一定能提升程序性能,但是它希望做到的是用少量线程和内存提升伸缩性,及时响应新请求。

猜你喜欢

转载自blog.csdn.net/qq_20597727/article/details/80784438