JAVA協程 纖程 與Quasar 框架


ava使用的是系統級線程,也就是說,每次調用new Thread(....).run(),都會在系統層面建立一個新的線程,然鵝新建線程的開銷是很大的(每個線程默認情況下會占用1MB的內存空間,當然你願意的話可以用-Xss來調小點),更不要說線程切換帶來的開銷了

為了節省開銷,程序員玩出了很多花樣。

最常用的是線程池(線程復用,但是完全無法處理阻塞調用的問題)

以及事件驅動框架(NIO或者Netty,用少量的工作線程來服務大量的慢速IO連接,但是EventLoop中也不能有阻塞調用,耗時的邏輯必須放在額外的線程池里處理)

但是NIO的代碼難寫也難懂,像我這種懶惰的程序猴子,最喜歡的還是一個線程對應一個連接這種簡單粗暴的編程手法。

 

纖程(Coroutine)是我們的救星

所謂的纖程,或者協程,可以理解為是一種輕量級的線程,它與線程的主要區別在於

a. 線程切換的過程是由系統內核完成,切換的過程中會進入到內核態。而纖程則完全工作在用戶態。

b. 線程是否發生切換是由操作系統決定的(搶占式調度),工作線程本身沒有決定權。而纖程的切換是需要工作纖程主動放棄CPU,這樣調度器才能讓另外一個纖程繼續運行。

 

很多語言已經內置了纖程,最著名的應該就是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%)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM