ava使用的是系統級線程,也就是說,每次調用new Thread(....).run(),都會在系統層面創建一個新的線程,然鵝新建線程的開銷是很大的(每一個線程默認狀況下會佔用1MB的內存空間,固然你願意的話能夠用-Xss來調小點),更不要說線程切換帶來的開銷了git
爲了節省開銷,程序員玩出了不少花樣。程序員
最經常使用的是線程池(線程複用,可是徹底沒法處理阻塞調用的問題)github
以及事件驅動框架(NIO或者Netty,用少許的工做線程來服務大量的慢速IO鏈接,可是EventLoop中也不能有阻塞調用,耗時的邏輯必須放在額外的線程池裏處理)編程
可是NIO的代碼難寫也難懂,像我這種懶惰的程序猴子,最喜歡的仍是一個線程對應一個鏈接這種簡單粗暴的編程手法。併發
纖程(Coroutine)是咱們的救星框架
所謂的纖程,或者協程,能夠理解爲是一種輕量級的線程,它與線程的主要區別在於高併發
a. 線程切換的過程是由系統內核完成,切換的過程當中會進入到內核態。而纖程則徹底工做在用戶態。oop
b. 線程是否發生切換是由操做系統決定的(搶佔式調度),工做線程自己沒有決定權。而纖程的切換是須要工做纖程主動放棄CPU,這樣調度器才能讓另一個纖程繼續運行。spa
不少語言已經內置了纖程,最著名的應該就是Go了,用go關鍵字,就能直接建立一個纖程並在其中隨心所欲,其餘的Scheduler會自動幫你搞定。因此Go能相對容易的寫出正確的高併發程序。操作系統
惋惜的是,Java沒有官方的纖程支持,好在有個叫作Quasar的庫可堪一用
使用這個lib,你就能在Java程序中建立纖程了,代碼大概長這個樣子:
public static void main(String[] args) throws ExecutionException, InterruptedException, SuspendExecution { int FiberNumber = 1_000_000; CountDownLatch latch = new CountDownLatch(1); AtomicInteger counter = new AtomicInteger(0); for (int i = 0; i < FiberNumber; i++) { new Fiber(() -> { counter.incrementAndGet(); if (counter.get() == FiberNumber) { System.out.println("done"); } Strand.sleep(1000000); }).start(); } latch.await(); }
在上面這段代碼中,咱們直接建立了一百萬個纖程,若是是通常的Thread,不考慮OS可否負擔得起,單單佔用的內存就要1T起步。
可是這段程序實際佔用的內存只在1G出頭,也就是說每一個纖程的內存佔用只在1K左右。
這是如何作到的?
Quasar在編譯時會對代碼進行掃描,若是方法帶有Suspendable註解,或者拋出SuspendExecution,或者在配置文件中被指定,Quasar會直接修改生成的字節碼,在park方法的先後,插入一些字節碼。
這些字節碼會記錄此時纖程的執行狀態(相關的局部變量與操做數棧),而後經過拋出異常的方式將CPU的控制權從當前協程交回到控制器
此時控制器能夠再次調度另一個纖程運行,並經過以前插入的那些字節碼恢復當前纖程的執行狀態,使程序能繼續正常執行。
而且,這些操做是很是輕量的,因此內存消耗極小,也不會對CPU帶來太多的額外開銷(聽說在3%-5%)