自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java不支持協(xié)程?那是你不知道Quasar!

開發(fā) 前端
本文介紹了Quasar框架的簡(jiǎn)單使用,其具體的實(shí)現(xiàn)原理比較復(fù)雜,暫時(shí)就不在這里進(jìn)行討論,后面打算單獨(dú)拎出來(lái)進(jìn)行分析。

在編程語(yǔ)言的這個(gè)圈子里,各種語(yǔ)言之間的對(duì)比似乎就一直就沒(méi)有停過(guò),像什么古早時(shí)期的"PHP是世界上最好的語(yǔ)言"就不提了,最近我在摸魚的時(shí)候,看到不少文章都在說(shuō)"Golang性能吊打Java"。作為一個(gè)寫了好幾年java的javaer,這我怎么能忍?于是在網(wǎng)上看了一些對(duì)比golang和java的文章,其中戳中java痛點(diǎn)、也是golang被吹上天的一條,就是對(duì)多線程并發(fā)的支持了。先看一段描述:

Go從語(yǔ)言層面原生支持并發(fā),并且使用簡(jiǎn)單,Go語(yǔ)言中的并發(fā)基于輕量級(jí)線程Goroutine,創(chuàng)建成本很低,單個(gè)Go應(yīng)用也可以充分利用CPU多核,編寫高并發(fā)服務(wù)端軟件簡(jiǎn)單,執(zhí)行性能好,很多情況下完全不需要考慮鎖機(jī)制以及由此帶來(lái)的各種問(wèn)題。

看到這,我的心瞬間涼了大半截,真的是字字扎心。雖然說(shuō)java里的JUC包已經(jīng)幫我們封裝好了很多并發(fā)工具,但實(shí)際高并發(fā)的環(huán)境中我們還要考慮到各種鎖的使用,以及服務(wù)器性能瓶頸、限流熔斷等非常多方面的問(wèn)題。

再說(shuō)回go,前面提到的這個(gè)goroutine究竟是什么東西?其實(shí),輕量級(jí)線程goroutine也可以被稱為協(xié)程,得益于go中的調(diào)度器以及GMP模型,go程序會(huì)智能地將goroutine中的任務(wù)合理地分配給每個(gè) CPU。

好了,其實(shí)上面說(shuō)的這一大段我也不懂,都是向?qū)慻o的哥們兒請(qǐng)教來(lái)的,總之就是go的并發(fā)性能非常優(yōu)秀就是了。不過(guò)這都不是我們要說(shuō)的重點(diǎn),今天我們要討論的是如何在Java中使用協(xié)程。

協(xié)程是什么?

我們知道,線程在阻塞狀態(tài)和可運(yùn)行狀態(tài)的切換,以及線程間的上下文切換都會(huì)造成性能的損耗。為了解決這些問(wèn)題,引入?yún)f(xié)程coroutine這一概念,就像在一個(gè)進(jìn)程中允許存在多個(gè)線程,在一個(gè)線程中,也可以存在多個(gè)協(xié)程。

那么,使用協(xié)程究竟有什么好處呢?

首先,執(zhí)行效率高。線程的切換由操作系統(tǒng)內(nèi)核執(zhí)行,消耗資源較多。而協(xié)程由程序控制,在用戶態(tài)執(zhí)行,不需要從用戶態(tài)切換到內(nèi)核態(tài),我們也可以理解為,協(xié)程是一種進(jìn)程自身來(lái)調(diào)度任務(wù)的調(diào)度模式,因此協(xié)程間的切換開銷遠(yuǎn)小于線程切換。

其次,節(jié)省資源。因?yàn)閰f(xié)程在本質(zhì)上是通過(guò)分時(shí)復(fù)用了一個(gè)單線程,因此能夠節(jié)省一定的資源。

類似于線程的五種狀態(tài)切換,協(xié)程間也存在狀態(tài)的切換,下面這張圖展示了協(xié)程調(diào)度器內(nèi)部任務(wù)的流轉(zhuǎn)。

綜合上面這些角度來(lái)看,和原生支持協(xié)程的go比起來(lái),java在多線程并發(fā)上還真的是不堪一擊。但是,雖然在Java官方的jdk中不能直接使用協(xié)程,但是,有其他的開源框架借助動(dòng)態(tài)修改字節(jié)碼的方式實(shí)現(xiàn)了協(xié)程,就比如我們接下來(lái)要學(xué)習(xí)的Quasar。

Quasar使用

Quasar是一個(gè)開源的Java協(xié)程框架,通過(guò)利用Java instrument技術(shù)對(duì)字節(jié)碼進(jìn)行修改,使方法掛起前后可以保存和恢復(fù)jvm棧幀,方法內(nèi)部已執(zhí)行到的字節(jié)碼位置也通過(guò)增加狀態(tài)機(jī)的方式記錄,在下次恢復(fù)執(zhí)行可直接跳轉(zhuǎn)至最新位置。

Quasar項(xiàng)目最后更新時(shí)間為2018年,版本停留在0.8.0,但是我在直接使用這個(gè)版本時(shí)報(bào)了一個(gè)錯(cuò)誤:

這個(gè)錯(cuò)誤的大意就是這個(gè)class文件是使用的高版本jdk編譯的,所以你在低版本的jdk上當(dāng)然無(wú)法運(yùn)行了。這里major版本號(hào)54對(duì)應(yīng)的是jdk10,而我使用的是jdk8,無(wú)奈降級(jí)試了一下低版本,果然0.7.10可以使用:

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

在我們做好準(zhǔn)備工作后,下面就寫幾個(gè)例子來(lái)感受一下協(xié)程的魅力吧。

1、運(yùn)行時(shí)間

下面我們模擬一個(gè)簡(jiǎn)單的場(chǎng)景,假設(shè)我們有一個(gè)任務(wù),平均執(zhí)行時(shí)間為1秒,分別測(cè)試一下使用線程和協(xié)程并發(fā)執(zhí)行10000次需要消耗多少時(shí)間。

先通過(guò)線程進(jìn)行調(diào)用,直接使用Executors線程池:

public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch=new CountDownLatch(10000);
long start = System.currentTimeMillis();
ExecutorService executor= Executors.newCachedThreadPool();
for (int i = 0; i < 10000; i++) {
executor.submit(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("Thread use:"+(end-start)+" ms");
}

查看運(yùn)行時(shí)間:

好了,下面我們?cè)儆肣uasar中的協(xié)程跑一下和上面相同的流程。這里我們要使用的是Quasar中的Fiber,它可以被翻譯為協(xié)程或纖程,創(chuàng)建Fiber的類型主要可分為下面兩類:

public Fiber(String name, FiberScheduler scheduler, int stackSize, SuspendableRunnable target);
public Fiber(String name, FiberScheduler scheduler, int stackSize, SuspendableCallable<V> target);

在Fiber中可以運(yùn)行無(wú)返回值的SuspendableRunnable或有返回值的SuspendableCallable,看這個(gè)名字也知道區(qū)別就是java中的Runnable和Callable的區(qū)別了。其余參數(shù)都可以省略,name為協(xié)程的名稱,scheduler是調(diào)度器,默認(rèn)使用FiberForkJoinScheduler,stackSize指定用于保存fiber調(diào)用棧信息的stack大小。

在下面的代碼中,使用了Fiber.sleep()方法進(jìn)行協(xié)程的休眠,和Thread.sleep()非常類似。

public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch=new CountDownLatch(10000);
long start = System.currentTimeMillis();

for (int i = 0; i < 10000; i++) {
new Fiber<>(new SuspendableRunnable(){
@Override
public Integer run() throws SuspendExecution, InterruptedException {
Fiber.sleep(1000);
countDownLatch.countDown();
}
}).start();
}

countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("Fiber use:"+(end-start)+" ms");
}

直接運(yùn)行,報(bào)了一個(gè)警告:

QUASAR WARNING: Quasar Java Agent isn't running. If you're using another instrumentation method you can ignore this message; otherwise, please refer to the Getting Started section in the Quasar documentation.

還記得我們前面說(shuō)過(guò)的Quasar生效的原理是基于Java instrument技術(shù)嗎,所以這里需要給它添加一個(gè)代理Agent。找到本地maven倉(cāng)庫(kù)中已經(jīng)下好的jar包,在VM options中添加參數(shù):

-javaagent:E:\Apache\maven-repository\co\paralleluniverse\quasar-core\0.7.10\quasar-core-0.7.10.jar

這次運(yùn)行時(shí)就沒(méi)有提示警告了,查看一下運(yùn)行時(shí)間:

運(yùn)行時(shí)間只有使用線程池時(shí)的一半多一點(diǎn),確實(shí)能大大縮短程序的效率。

2、內(nèi)存占用

在測(cè)試完運(yùn)行時(shí)間后,我們?cè)賮?lái)測(cè)試一下運(yùn)行內(nèi)存占用的對(duì)比。通過(guò)下面代碼嘗試在本地啟動(dòng)100萬(wàn)個(gè)線程:

public static void main(String[] args) {
for (int i = 0; i < 1000000; i++) {
new Thread(() -> {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}

本來(lái)以為會(huì)報(bào)OutOfMemoryError,但是沒(méi)想到的是我的電腦直接直接卡死了…而且不是一次,試了幾次都是以卡死只能重啟電腦而結(jié)束。好吧,我選擇放棄,那么下面再試試啟動(dòng)100萬(wàn)個(gè)Fiber協(xié)程。

public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch=new CountDownLatch(10000);
for (int i = 0; i < 1000000; i++) {
int finalI = i;
new Fiber<>((SuspendableCallable<Integer>)()->{
Fiber.sleep(100000);
countDownLatch.countDown();
return finalI;
}).start();
}
countDownLatch.await();
System.out.println("end");
}

程序能夠正常執(zhí)行結(jié)束,看樣子使用的內(nèi)存真的比線程少很多。上面我故意使每個(gè)協(xié)程結(jié)束的時(shí)間拖得很長(zhǎng),這樣我們就可以在運(yùn)行過(guò)程中使用Java VisualVM查看內(nèi)存的占用情況了:

可以看到在使用Fiber的情況下只使用了1G多一點(diǎn)的內(nèi)存,平均到100萬(wàn)個(gè)協(xié)程上也就是說(shuō)每個(gè)Fiber只占用了1Kb左右的內(nèi)存空間,和Thread線程比起來(lái)真的是非常的輕量級(jí)。

從上面這張圖中我們也可以看到,運(yùn)行了非常多的ForkJoinPool,它們又起到了什么作用呢?我們?cè)谇懊嬲f(shuō)過(guò),協(xié)程是由程序控制在用戶態(tài)進(jìn)行切換,而Quasar中的調(diào)度器就使用了一個(gè)或多個(gè)ForkJoinPool來(lái)完成對(duì)Fiber的調(diào)度。

3、原理與應(yīng)用

這里簡(jiǎn)單介紹一下Quasar的原理,在編譯時(shí)框架會(huì)對(duì)代碼進(jìn)行掃描,如果方法帶有@Suspendable注解,或拋出了SuspendExecution,或在配置文件META-INF/suspendables中指定該方法,那么Quasar就會(huì)修改生成的字節(jié)碼,在park掛起方法的前后,插入一些字節(jié)碼。

這些字節(jié)碼會(huì)記錄此時(shí)協(xié)程的執(zhí)行狀態(tài),例如相關(guān)的局部變量與操作數(shù)棧,然后通過(guò)拋出異常的方式將cpu的控制權(quán)從當(dāng)前協(xié)程交回到控制器,此時(shí)控制器可以再調(diào)度另外一個(gè)協(xié)程運(yùn)行,并通過(guò)之前插入的那些字節(jié)碼恢復(fù)當(dāng)前協(xié)程的執(zhí)行狀態(tài),使程序能繼續(xù)正常執(zhí)行。

回頭看一下前面例子中的SuspendableRunnable和SuspendableCallable,它們的run方法上都拋出了SuspendExecution,其實(shí)這并不是一個(gè)真正的異常,僅作為識(shí)別掛起方法的聲明,在實(shí)際運(yùn)行中不會(huì)拋出。當(dāng)我們創(chuàng)建了一個(gè)Fiber,并在其中調(diào)用了其他方法時(shí),如果想要Quasar的調(diào)度器能夠介入,那么必須在使用時(shí)層層拋出這個(gè)異?;蛱砑幼⒔狻?/p>

看一下簡(jiǎn)單的代碼書寫的示例:

public void request(){
new Fiber<>(new SuspendableRunnable() {
@Override
public void run() throws SuspendExecution, InterruptedException {
String content = sendRequest();
System.out.println(content);
}
}).start();
}

private String sendRequest() throws SuspendExecution {
return realSendRequest();
}

private String realSendRequest() throws SuspendExecution{
HttpResponse response = HttpRequest.get("http://127.0.0.1:6879/name").execute();
String content = response.body();
return content;
}

需要注意的是,如果在方法內(nèi)部已經(jīng)通過(guò)try/catch的方式捕獲了Exception,也應(yīng)該再次手動(dòng)拋出這個(gè)SuspendExecution異常。

總結(jié)

本文介紹了Quasar框架的簡(jiǎn)單使用,其具體的實(shí)現(xiàn)原理比較復(fù)雜,暫時(shí)就不在這里進(jìn)行討論,后面打算單獨(dú)拎出來(lái)進(jìn)行分析。另外,目前已經(jīng)有不少其他的框架中已經(jīng)集成了Quasar,例如同樣是Parallel Universe下的Comsat項(xiàng)目,能夠提供了HTTP和DB訪問(wèn)等功能。

雖然現(xiàn)在想要在Java中使用協(xié)程還只能使用這樣的第三方的框架,但是也不必灰心,在OpenJDK 16中已經(jīng)加入了一個(gè)名為Project Loom的項(xiàng)目, 在OpenJDK Wiki上可以看到對(duì)它的介紹,它將使用Fiber輕量級(jí)用戶模式線程,從jvm層面對(duì)多線程技術(shù)進(jìn)行徹底的改變,使用新的編程模型,使輕量級(jí)線程的并發(fā)也能夠適用于高吞吐量的業(yè)務(wù)場(chǎng)景。

責(zé)任編輯:姜華 來(lái)源: 碼農(nóng)參上
相關(guān)推薦

2010-08-23 09:56:09

Java性能監(jiān)控

2020-06-12 09:20:33

前端Blob字符串

2020-07-28 08:26:34

WebSocket瀏覽器

2011-09-15 17:10:41

2022-10-13 11:48:37

Web共享機(jī)制操作系統(tǒng)

2009-12-10 09:37:43

2021-02-01 23:23:39

FiddlerCharlesWeb

2020-09-15 08:35:57

TypeScript JavaScript類型

2022-11-04 08:19:18

gRPC框架項(xiàng)目

2021-12-29 11:38:59

JS前端沙箱

2021-12-22 09:08:39

JSON.stringJavaScript字符串

2012-11-23 10:57:44

Shell

2015-06-19 13:54:49

2020-08-11 11:20:49

Linux命令使用技巧

2016-07-22 17:55:07

云計(jì)算

2021-10-17 13:10:56

函數(shù)TypeScript泛型

2021-04-20 19:23:07

語(yǔ)法switch-casePython

2020-04-27 10:34:23

HTTPDNSDNS網(wǎng)絡(luò)協(xié)議

2012-06-26 15:49:05

2014-03-12 09:23:06

DevOps團(tuán)隊(duì)合作
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)