quasar 开源地址:https://github.com/puniverse/quasar
协程本质:单线程实现并行。
协程:适用于 IO 密集型。
线程池:适用于计算密集型。
Demo
假设我们有 100 个任务,每个任务需要做大量 IO (用 sleep 10秒 模拟)。
线程池实现
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(8);
List<Future> futures = new ArrayList<>();
for (int i = 0; i < 100; i++) {
final int count = i;
Callable<Integer> callable = () -> {
Thread.sleep(10000);
return count;
};
Future future = executorService.submit(callable);
futures.add(future);
}
System.out.println("start" + " " + LocalDateTime.now().toString());
Set<Future> finshed = new HashSet<>();
while (finshed.size() < 100) {
for (Future future : futures) {
if (future.isDone() && !finshed.contains(future)) {
System.out.println(future.get() + " " + LocalDateTime.now().toString());
finshed.add(future);
}
}
}
}
开 8 个线程并行。
结果:
可见,耗费时间为 Math.ceil(100.0 / 8) * 10秒 : 130 秒。
start 2020-01-19T00:01:31.997
1 2020-01-19T00:01:41.978
2 2020-01-19T00:01:41.978
.......
99 2020-01-19T00:03:42.027
协程实现
<dependency>
<groupId>co.paralleluniverse</groupId>
<artifactId>quasar-core</artifactId>
<version>0.7.10</version>
<classifier>jdk8</classifier>
</dependency>
public static void main(String[] args) throws Exception {
List<Fiber<Integer>> fibers = new ArrayList<>();
for (int i = 0; i < 100; i++) {
final int count = i;
Fiber<Integer> fiber = new Fiber<>((SuspendableCallable<Integer>) () -> {
Fiber.sleep(100000);
return count;
});
fiber.start();
fibers.add(fiber);
}
for (Fiber fiber : fibers) {
System.out.println(fiber.get() + " " + LocalDateTime.now().toString());
}
}
看看有多少个线程,发现:多了:
1 个 FiberTimedScheduler-default-fiber-pool
8 个 ForkJoinPool-default-fiber-pool-worker
也就是说对于 100 个任务,协程只用了 8 个 worker 线程。(为什么不是 1 个 worker 线程呢?可参考:https://blog.csdn.net/justsomebody126/article/details/104022982。协程与线程之间时多对多,这也是为了利用多 CPU 的特性。)
看输出结果,可见:
100 个任务同时完成。也就是说 8 个 worker 线程,每个 worker 负责的 12 个任务不是串行的,这也就是协程的本质:
单线程实现并行。
对于 IO 占时很长的任务来说,协程明显非常有优势。在同一个 Worker 线程中,如果当前任务遇到 IO,则把时间片让出给其他任务。这也是为什么所有任务能够同时完成。
0 2020-01-18T23:27:30.701
1 2020-01-18T23:27:30.702
2 2020-01-18T23:27:30.702
......
97 2020-01-18T23:27:30.706
98 2020-01-18T23:27:30.706
99 2020-01-18T23:27:30.706
内存消耗
- 线程
如果使用线程来并发,想要并发度高,就要增加线程数。 但 JVM 每个线程默认栈内存 1M。
最大线程数量 =(Xmx - JVM分配的堆内存)/ Xss。显然线程数量大大受限。
- 协程
而对于协程来说,其实多个协程只用到了1个线程。但这也并不代表每个协程自身就不占内存,但非常少,https://www.cnblogs.com/ll409546297/p/10945119.html 这篇文章说是 1K。
其他参考:
https://colobu.com/2016/07/14/Java-Fiber-Quasar/ 这两个讲 Quasar 的基本原理的。
https://colobu.com/2016/08/01/talk-about-quasar-again/
http://docs.paralleluniverse.co/quasar/
https://zhuanlan.zhihu.com/p/27519705
https://zhuanlan.zhihu.com/p/27572086
https://zhuanlan.zhihu.com/p/27590299 java 的协程库
https://blog.csdn.net/guzhangyu12345/article/details/84666423 java 协程 quasar 从原理到代码应用
https://blog.csdn.net/guzhangyu12345/article/details/84257764 java协程之quasar初窥
微信协程改造:https://www.infoq.cn/article/CplusStyleCorourtine-At-Wechat/
不管是进程还是线程,每次阻塞、切换都需要陷入系统调用(system call),先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个进程(线程)。而且由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题(事件驱动和异步程序也有同样的优点)