溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java協(xié)程框架quasar和kotlin中的協(xié)程實(shí)例分析

發(fā)布時間:2022-02-25 09:28:40 來源:億速云 閱讀:170 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“java協(xié)程框架quasar和kotlin中的協(xié)程實(shí)例分析”的相關(guān)知識,小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“java協(xié)程框架quasar和kotlin中的協(xié)程實(shí)例分析”文章能幫助大家解決問題。

    前言

    早就聽說Go語言開發(fā)的服務(wù)不用任何架構(gòu)優(yōu)化,就可以輕松實(shí)現(xiàn)百萬級別的qps。這得益于Go語言級別的協(xié)程的處理效率。協(xié)程不同于線程,線程是操作系統(tǒng)級別的資源,創(chuàng)建線程,調(diào)度線程,銷毀線程都是重量級別的操作。而且線程的資源有限,在java中大量的不加限制的創(chuàng)建線程非常容易將系統(tǒng)搞垮。接下來要分享的這個開源項(xiàng)目,正是解決了在java中只能使用多線程模型開發(fā)高并發(fā)應(yīng)用的窘境,使得java也能像Go語言那樣使用協(xié)程的語義開發(fā)了。

    快速體驗(yàn)

    添加依賴

    <dependency>
                <groupId>co.paralleluniverse</groupId>
                <artifactId>quasar-core</artifactId>
                <version>0.7.10</version>
    </dependency>

    注意:目前quasar最高的版本是0.8.0,但是最高版本的只支持jdk11以上

    添加java agent

    quasar的實(shí)現(xiàn)原理是在java加載class前,通過jdk的instrument機(jī)制使用asm來修改目標(biāo)class的字節(jié)碼來實(shí)現(xiàn)的,他標(biāo)記了協(xié)程代碼的起始和結(jié)束的位置,以及方法需要暫停的位置,每個協(xié)程任務(wù)統(tǒng)一由FiberScheduler去調(diào)度,內(nèi)部維護(hù)了一個或多個ForkJoinPool實(shí)例。所以,在運(yùn)行應(yīng)用前,需要配置好quasar-core的java agent地址,在vm參數(shù)上加上如下腳本即可:

    -javaagent:D:\.m2\repository\co\paralleluniverse\quasar-core\0.7.10\quasar-core-0.7.10.jar

    線程VS協(xié)程

    下面模擬調(diào)用某個遠(yuǎn)程的服務(wù),假設(shè)遠(yuǎn)程服務(wù)處理耗時需要1S,這里使用執(zhí)行阻塞1S來模擬,分別看多線程模型和協(xié)程模型調(diào)用這個服務(wù)10000次所需的耗時

    協(xié)程代碼

    public static void main(String[] args) throws Exception{
            CountDownLatch count  = new CountDownLatch(10000);
            StopWatch stopWatch = new StopWatch();stopWatch.start();
            IntStream.range(0,10000).forEach(i-> new Fiber() {
                @Override
                protected String run() throws SuspendExecution, InterruptedException {
                    Strand.sleep(1000 );
                    count.countDown();
                    return  "aa";
                }
            }.start());
            count.await();stopWatch.stop();
            System.out.println("結(jié)束了: " + stopWatch.prettyPrint());
        }

    耗時情況:

    java協(xié)程框架quasar和kotlin中的協(xié)程實(shí)例分析

    多線程代碼

    public static void main(String[] args) throws Exception{
            CountDownLatch count  = new CountDownLatch(10000);
            StopWatch stopWatch = new StopWatch();stopWatch.start();
            ExecutorService executorService = Executors.newCachedThreadPool();
            IntStream.range(0,10000).forEach(i-> executorService.submit(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException ex) { }
                count.countDown();
            }));
            count.await();stopWatch.stop();
            System.out.println("結(jié)束了: " + stopWatch.prettyPrint());
        }

    耗時情況

    java協(xié)程框架quasar和kotlin中的協(xié)程實(shí)例分析

    協(xié)程完勝

    可以看到上面的結(jié)果,在對比訪問一個耗時1s的服務(wù)10000次時,協(xié)程只需要2秒多,而多線程模型需要4秒多,時效相差了一倍。而且上面多線程編程時,并沒有指定線程池的大小,在實(shí)際開發(fā)中是絕不允許的。一般我們會設(shè)置一個固定大小的線程池,因?yàn)榫€程資源是寶貴,線程多了費(fèi)內(nèi)存還會帶來線程切換的開銷。上面的場景在設(shè)置200個固定大小線程池時。結(jié)果也是可預(yù)見的達(dá)到了50多秒。這個結(jié)果足以證明協(xié)程編程ko線程編程了。而且在qps越大時,線程處理的效率和協(xié)程的差距就約明顯,縮小差距的唯一方式就是增加線程數(shù),而這帶來的影響就是內(nèi)存消耗激增。而反觀協(xié)程,基于固定的幾個線程調(diào)度,可以輕松實(shí)現(xiàn)百萬級的協(xié)程處理,而且內(nèi)存穩(wěn)穩(wěn)的。

    后記

    最后,博主以為Quasar只是一個框架層面的東西,所以就又去看了下同樣是jvm語言的kotlin的協(xié)程。他的語言更簡潔,可以直接和java混合使用。跑上面這種實(shí)例只需要1秒多。

    fun main() {
        val count = CountDownLatch(10000)
        val stopWatch = StopWatch()
        stopWatch.start()
        IntStream.range(0,10000).forEach {
            GlobalScope.launch {
                delay(1000L)
                println(Thread.currentThread().name + "->"+ it)
                count.countDown()
            }
        }
        count.await()
        stopWatch.stop()
        println("結(jié)束了: " + stopWatch.prettyPrint())
    }

    當(dāng)博主看到這個結(jié)果的時候,有種震驚的趕腳,kotlin的同步模型牛逼呀,瞬時感覺到發(fā)現(xiàn)了java里的騷操作了,可以使用kotlin的協(xié)程來代替java中的多線程操作。因?yàn)樗麄儍蓚€混合開發(fā)毫無壓力。如果行的通,那就太爽了。所以就有下面這個kotlin協(xié)程實(shí)現(xiàn)的代碼:

    @Service
    class KotlinAsyncService(private val weatherService: GetWeatherService,private val demoApplication: DemoApplication){
        val weatherUrl = "http://localhost:8080/demo/mockWeatherApi?city="
        fun getHuNanWeather(): JSONObject{
            val result = JSONObject()
            val count = CountDownLatch(demoApplication.weatherContext.size)
            for (city in demoApplication.weatherContext){
                val url = weatherUrl + city.key
                GlobalScope.launch {
                    result[city.key.toString()] = weatherService.get(url)
                    count.countDown()
                }
            }
            count.await()
            return result
        }
    }

    現(xiàn)實(shí)是,當(dāng)我使用協(xié)程替換掉我java多線程寫的一個多線程匯聚多個http接口的結(jié)果的接口時,通過ab壓測他們兩個的性能并沒有很大的變化,最后了解到主要原因是這個時候,在協(xié)程里發(fā)起一個http的請求時,涉及到操作系統(tǒng)層面的socket io操作,io操作是阻塞的,協(xié)程的并發(fā)也就變成了調(diào)度協(xié)程的幾個線程的并發(fā)了。而且當(dāng)我把同樣的代碼放到Quasar中的時候,Quasar直接拋io異常了,說明Quasar還并不能輕松支持這個場景。那為什么上面的測試結(jié)果差距這么大呢,是因?yàn)槲义e誤的把協(xié)程實(shí)現(xiàn)里的阻塞等同于線程的阻塞。協(xié)程里的delay掛起函數(shù),會立馬釋放線程到線程池,但是當(dāng)真正的io阻塞的時候也就和真正的線程sleep一樣了,并沒有釋放當(dāng)前的線程。所以這些對比都沒有太大的意義

    關(guān)于“java協(xié)程框架quasar和kotlin中的協(xié)程實(shí)例分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點(diǎn)。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI