同步OR异步?WebFlux开发真的比Servlet开发要快?顺便再科普下CompletableFuture
在看下文之前,先给大家科普一点基础知识
Runable:线程任务类接口,没有返回值
Callable:与上面的不同就是有返回值
Executor:定义了线程池执行任务的接口,不过只定义了Runable的,也就是execute
ExecutorService:是线程池的规范,像ThreadPoolExecutor就是它的实现类,继承了Executor,并且自己还扩展了对于Callable任务类的执行规范,也就是submit()
Executors:一个工具类,提供了一些有默认配置的线程池,如new FixedThreadPool(int num),其实除了newWorkStealingPool(使用的是ForkJoinPool)外使用的都是ThreadPoolExecutor,包括ScheduledThreadPoolExecutor,定时器线程池,只是队列使用的是
DelayedWorkQueue这种延时阻塞队列.
Future: java对于future模式实现的接口
FutureTask:Future接口的实现类
首先,请大家思考一个问题,异步与同步是什么?区别在哪里?看清楚,这里说的是异步和同步,而不是阻塞与非阻塞(阻塞与非阻塞的区别是在于线程是否被阻塞的概念,也就是碰到一个会阻塞的代码(如IO)你是开启子线程去运行让主线程直接过还是让主线程去阻塞等着).
相比于阻塞和非阻塞这种定义非常清晰直观的观念,同步与异步的概念则非常模糊,在我刚接触NIO与AIO的时候因为网上各种博客的误导以及自己也没有花时间思考其本质(独立思考很重要,不要只是被动接受),导致一度理解为同步和异步的概念是操作系统级别的,也就是这个任务的触发是系统主动通知你(回调)的,还是你在程序里不停的轮询查看是否达到触发条件的.
其实,这只是表面现象,大家想想,程序主动访问和系统通知你,这两个真正的区别在于什么?其实是在于一个是需要我们不停的轮询,不停的人为查看.而另一个则是通过回调函数,也就是由完成任务的事件触发的,不需要我们去人为观察的.这才是同步和异步的区别,同步在某一个点上仍然需要去主动访问我们的任务线程.而异步,则是任务线程自己告诉我们,我们已经完成了.也就是说,它们真正的区别在于,有没有完成任务后的回调函数!
理解到了这个,我们再来说说对于CompletableFuture,首先,此类是java第一个真正的异步编程实现,FutureTask则是同步,原因也很简单,你需要主动去拿FutureTask里的返回值(get()方法),也就顺应了我们上文对于同步的观点,同步是需要我们程序去主动访问我们的任务线程
CompletableFuture则不需要我们主动获取,原因也很简单,CompletableFuture是基于事件驱动的数据处理.也就是说,CompletableFuture可以注册监听器,这样一旦我们的数据处理完成就会自动触发回调
下面是一个小的demo
从打印的信息来分析
1.在我们没有指定线程池的情况下CompletableFuture默认使用的是ForkjoinPool,而ForkjoinPoll的线程则是守护线程,这也就是我为什么要在最后join的原因.(守护线程的生存周期是依赖于前台线程的,也就是说,我主线程运行完了jvm就直接退出而不会管守护线程是否还在执行.)
2.虽然在链式的每一个调用里都指定了异步,整个方法链依然使用的是同一个线程,原因是这些调用都存在依赖关系,所以我一直觉得CompletetableFuture的链式调用挺鸡肋的.但是!虽然在链式调用里方法的运行是串行的,但对于外面调用它的线程来说,它依然是异步的(参考上文对与异步的定义)
上面之所以说CompletableFuture的概念,是因为CompletableFuture其实就是典型的响应式编程风格,为我们下面要说的WebFlux做个铺垫,所以我们下面再来说说WebFlux最大的误区,也就是响应式编程相比于Servlet效率更高,性能更好,响应时间更短
大家都知道,Spring5推出了一个WebFlux开发框架,(使用Netty做的默认服务器,并且不使用Servlet规范),利用Mono(对应单例)和WebFlux(对应多例)
我们看下面这个例子
首先请注意,Mono是Netty提供的实现而非Spring,之后再请大家思考一个过程,当我访问这个代码的时候,会发生什么呢?
Spring会去启动一个线程去处理Mono方法调用链,之后马上返回,看清楚,是启动,都不一定开始运行,可能只是就绪的状态就会返回(这里科普下线程生命周期的一个基础知识,当你启动一个线程后,此线程并非会马上运行,它还需要等待cpu的时间片)
但是!这里马上会返回的意思是处理Request请求的线程马上返回,而Response线程则不会马上响应给客户端,因为Mono线程还需要处理数据呢,而当Mono线程处理数据之后,会通过事件的形式被Response监听到,此时Response拿到数据后返回(此处可以理解为Mono在处理完数据后push给了Response)
Ok,这里大家应该都懂了吧,WebFlux框架在我们程序看来是异步的,但是在客户端响应看来还是同步的(因为我还是需要你返回给我数据我才可以响应给客户端,从某点来说对于客户端的即时响应这种事情,除非是一些后台任务,也就是无关响应内容的任务,否则都是没办法做到异步的),
也正因此,在WebFlux和传统Servlet开发都可以使用多线程的情况下WebFlux框架完全没有比之前Request和Response同步处理快的点,甚至可能会更慢,因为在开启了大量线程的情况下,线程上下文切换是一个很大的开销.要知道,WebFlux开发始终比传统开发要多开一个线程
而且最搞笑的一点是,Spring WebFlux框架的这种做法和Servlet 3(异步处理任务,Request即时返回)殊途同归.所以WebFlux框架比Servlet效率更高,响应更快这种说法不攻自破。
所以结论就是,webflux框架真的只是一种编程风格框架,就跟java8提供的函数式编程框架Stream一样,只能降低而不能提高任何性能,webflux框架能实现的,用其它方式一样能做到。