最近接触到响应式编程的概念,简单了解了一下在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的编程方式,不一定能提升程序性能,但是它希望做到的是用少量线程和内存提升伸缩性,及时响应新请求。