预备知识
进程(process)和线程(thread)是操作系统的基本概念, 网上有个很好的比喻来描述他们:
计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行
单个CPU一次只能运行一个进程, 就像一次只能启用一个车间,其他进程处于非运行状态
一个进程可以包括多个线程,就像一个车间里,可以有很多工人。他们协同完成一个任务
问题引出
假如现在你要集成一个系统, 请求 API 后做一些事情, 但是你不想阻塞前线程怎么办?
你可能首先想到的就是开新线程,或者用异步回调的方式.
的确是个好办法,让我们来 new 一个 Thread 吧!
然后系统中出现了遍地的 Thread, 而且Thread 的创建和销毁又耗时又浪费资源.
你说为何不用线程池(Thread Pool)?
不错!解决方案又来了,马上用上线程池,各种优化的配置, 系统哗啦啦性能上来了~
然而好景不长,系统越做越大,性能却又告急了.一看 CPU,却远远没有到瓶颈的地步.
如果我们抛开代码和环境问题来看, 首先线程池不是万能的,线程的数量始终有上限, 另外线程调度和上下文切换的时候是系统级别的,必定会有时间和性能消耗.
这种情况下这,如果还有一种东西可以在一个线程里继续 new 一些新的线程来工作, 而这些新的线程并不占用其他资源, 还是属于当前的线程里面, 也就是全是当前线程的产物, 最好还不怎么耗性能,想new多少就 new 多少.那该多好啊!
有吗?
有的!答案就是纤程!
关于协程( Coroutine )和纤程( Fiber )
协程和纤程在系统层面有着细微的差别, 但就语言级别而言是一致的.
以 Java 为栗子,我们主要称为纤程.协程和纤程对操作系统而言是完全不存在的, 也就是说他们的调度完全是开发人员行为.
所以理论上来说,纤程不存在上下文切换,不管创建多少纤程,都不会消耗太多性能,并且能让每个线程都处于运行状态,这样就可以充分利用资源,让系统 CPU 处于饱满状态.
既然操作系统不支持纤程, 那我们如何使用它呢?
事实上,大多数语言有自身的支持,比如 Go,Lua 等等,对于 Java 来说只能借助第三方框架实现.就最近比较火的 Quasar 为栗子吧,它的写法几乎和线程一样.
Quasar 的使用
首先引入 maven 依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<properties> <quasar-version>0.7.5</quasar-version> <quasar-groupId>co.paralleluniverse</quasar-groupId> <javaagent>{your-quasar-path}/quasar-core-0.7.5.jar</javaagent> </properties> <dependency> <groupId>${quasar-groupId}</groupId> <artifactId>quasar-core</artifactId> <version>${quasar-version}</version> </dependency> <dependency> <groupId>${quasar-groupId}</groupId> <artifactId>quasar-actors</artifactId> <version>${quasar-version}</version> </dependency> <dependency> <groupId>${quasar-groupId}</groupId> <artifactId>quasar-galaxy</artifactId> <version>${quasar-version}</version> </dependency> <dependency> <groupId>${quasar-groupId}</groupId> <artifactId>quasar-reactive-streams</artifactId> <version>${quasar-version}</version> </dependency> <dependency> <groupId>${quasar-groupId}</groupId> <artifactId>quasar-kotlin</artifactId> <version>${quasar-version}</version> </dependency> |
再引入 Java Agent 插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <!-- Run with "mvn compile maven-dependency-plugin:properties exec:exec" --> <version>1.3.2</version> <configuration> <mainClass>com.test.Main</mainClass> <workingDirectory>target/classes</workingDirectory> <executable>java</executable> <arguments> <!-- Turn off before production --> <argument>-Dco.paralleluniverse.fibers.verifyInstrumentation=true</argument> <!-- Quasar Agent --> <argument>-javaagent:${javaagent}</argument> <!-- Classpath --> <argument>-classpath</argument> <classpath/> <!-- Main class --> <argument>com.test.Main</argument> </arguments> </configuration> </plugin> |
之后就可以编码了:
1 2 3 4 |
new Fiber<Void>(() -> { Strand.sleep(3000); System.out.println("start..."); }).start(); |
附上Github 地址:
https://github.com/puniverse/quasar
Quasar 的优势和实现原理
1.异步不阻塞
2.不用切换线程,造成不必要的开销
3.理论上可以创建百万级别的 Fiber 达到高并发
4.不用担心调度问题,交给 Quassar
5.像写同步代码一样去写异步代码吧!
你可以想象,Quasar 其实通过 Java Agent 操作了字节码,把整个代码拆分成了很多你需要运行的代码块,然后放入一个大大的 switch case里面,当代码遇到阻塞的时候,跳去执行其他代码,当回调或响应的时候再跳回去继续执行.所以一个线程其实可以一直虚拟并发运行着,CPU 打满杠杠的!
