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跑操作系統的調度程序,然後再由調度程序決定該跑哪一個進程(線程)。而且由於搶佔式調度執行順序無法確定的特點,使用線程時需要非常小心地處理同步問題,而協程完全不存在這個問題(事件驅動和異步程序也有同樣的優點)