java 协程线程的区别_为什么 Java 坚持多线程不选择协程?
謝邀。
先說結論:協(xié)程是非常值得學習的概念,它是多任務編程的未來。但是Java全力推進這個事情的動力并不大。
先返回到問題的本源。當我們希望引入?yún)f(xié)程,我們想解決什么問題。我想不外乎下面幾點:節(jié)省資源,輕量,具體就是:節(jié)省內存,每個線程需要分配一段棧內存,以及內核里的一些資源
節(jié)省分配線程的開銷(創(chuàng)建和銷毀線程要各做一次syscall)
節(jié)省大量線程切換帶來的開銷
與NIO配合實現(xiàn)非阻塞的編程,提高系統(tǒng)的吞吐
使用起來更加舒服順暢(async+await,跑起來是異步的,但寫起來感覺上是同步的)
我們分開來講下。
先說內存。拿Java Web編程舉例子,一個tomcat上的woker線程池的最大線程數(shù)一般會配置為50~500之間(目前springboot的默認值給的200)。也就是說同一時刻可以接受的請求最多也就是這么多。如果超過了最大值,請求直接打失敗拒絕處理。假如每個線程給128KB,500個線程放一起的內存占用量大概是60+MB。如果真的有瓶頸,也許CPU,IO,帶寬,DB的CPU等會有瓶頸,但這點內存量的增幅對于動輒數(shù)個GB的Java運行時進程來說似乎并不是什么大問題。上面的討論簡化了RSS和VM的區(qū)別。實際上一個線程啟動后只會在虛擬地址上占位置那么多的內存。除非實際用上,是不會真的消耗物理內存的。
換一個場景,比如IM服務器,需要同時處理大量空閑的鏈接(可能要幾十萬,上百萬)。這時候用connection per thread就很不劃算了。但是可以直接改用netty去處理這類問題。你可以理解為NIO + woker thread大致就是一套“協(xié)程”,只不過沒有實現(xiàn)在語法層面,寫起來不優(yōu)雅而已。問題是,你的場景真的處理了并發(fā)幾十萬,上百萬的連接嗎?
再說創(chuàng)建/銷毀線程的開銷。這個問題在Java里通過線程池得到了很好的解決。你會發(fā)現(xiàn)即便你用vert.x或者kotlin的協(xié)程,歸根到底也是要靠線程池工作的。goroutine相當于設置一個全局的“線程池”,GOMAXPROCS就是線程池的最大數(shù)量;而Java可以自由設置多個不同的線程池(比如處理請求一套,異步任務另外一套等)。kotlin利用這個機制來構建多個不同的協(xié)程scope。這看起來似乎會更靈活一點。
然后是線程的切換開銷。線程的切換實際上只會發(fā)生在那些“活躍”的線程上。對于類似于Web的場景,大量的線程實際上因為IO(發(fā)請求/讀DB)而掛起,根本不會參與OS的線程切換。現(xiàn)實當中一個最大200線程的服務器可能同一時刻的“活躍線程”總數(shù)只有數(shù)十而已。其開銷沒有想象的那么大。為了避免過大的線程切換開銷,真正要防范的是同時有大量“活躍線程”。這個事情我自己上學的時候干過,當時是寫了一個網(wǎng)絡模擬器。每一個節(jié)點,每一個鏈路都由一個線程實現(xiàn)。模擬跑起來后,同時的活躍線程上千。當時整個機器瞬間卡死,直到kill掉這個程序。
此外說說與NIO的配合。在Java這個生態(tài)里Java NIO/Netty/Vert.X/rxJava/Akka可以任意選擇。一般來講,Netty可以解決絕大部分因為IO的等待造成資源浪費的問題。Vert.X/rxJava。可以讓程序寫的更加“優(yōu)雅”一點(見仁見智)。Akka就是Java世界里對“原教旨OO“的實現(xiàn),很有特色。的確,用NIO + completedFuture/handler/lambda不如async+await寫起來舒服,但起碼是可以干活的。
如果真的要較真Java的NIO用于業(yè)務的問題,其核心痛點應該是JDBC。這是個誕生了幾十年的,必須使用Blocking IO的DB交互協(xié)議。其上承載了Java龐大的生態(tài)和業(yè)務邏輯。Java要改自己的編程方式,必須得重新設計和實現(xiàn)JDBC,就像https://github.com/vert-x3/vertx-mysql-postgresql-client 那樣做。問題是,社區(qū)里這種“異步JDBC”還沒有支持oracle、sql server等傳統(tǒng)DB。對mysql和postgres的支持還需要繼續(xù)趟坑~
如果認真閱讀上面這些需要“協(xié)程”解決的問題,就會發(fā)現(xiàn)基本上都可以以各種方式解決。覺得線程耗資源,可以控制線程總數(shù),可以減少線程stack的大小,可以用線程池配置max和min idle等等。想要go的channel,可以上disruptor。可以說,Java這個生態(tài)里盡管沒有“協(xié)程”這個第一級別的概念,但是要解決問題的工具并不缺。
Java僅僅是沒有解決”協(xié)程“在Java中的定義,以及“寫得優(yōu)雅“這個問題。從工程角度,“寫得優(yōu)雅”的優(yōu)勢并沒有很多追新的人想象的那么關鍵。C#也并非因為有了async await就搶了Java的市場分毫。而反過來,如果java社區(qū)全力推進這個事情,Java歷史上的生態(tài)的積累卻因為協(xié)程的出現(xiàn)而進行大換血。想像一下如果沒有thread,也沒有ThreadLocal,@Transactional不起作用了,又沒有等價的工具,是不是很郁悶?這么看來怎么著都不是個劃算的事情。我想Oracle對此并不會有太大興趣。OpenJDK的loom能不能成,如果真的release多少Java程序員愿意使用,師母已呆。據(jù)我所知在9012年的今天,還有大量的Java6程序員。
其他新的語言歷史包袱少,比較容易重新思考“什么是現(xiàn)代的multi-task編程的方式“這個大主題。kotlin的協(xié)程、go的goroutine、javascript的async await、python的asyncio、swift的GCD都給了各自的答案。如果真的想入坑Java這個體系的“協(xié)程”,就從kotlin開始吧,畢竟可以混合編程。
最后說一句,多線程容易出bug主要因為:“搶占“式的線程切換 —— 你無法確定兩個線程訪問數(shù)據(jù)的順序,一切都很隨機
“同步“不可組裝 —— 同步的代碼組裝起來也不同步,必須加個更大的同步塊
協(xié)程能不能避免容易出bug的缺陷,主要看能不能避免上面兩個問題。如果協(xié)程底層用的還是線程池,兩個協(xié)程還是通過共享內存通訊,那么多線程該出什么bug,多協(xié)程照樣出。javascript里不出這種bug是因為其用戶線程就一個,不會出現(xiàn)線程切換,也不用同步;go是建議用channel做goroutine的通訊。如果go routine不用channel,而是用共享變量,并且沒有用Sync包控制一下,還是會出bug。
總結
以上是生活随笔為你收集整理的java 协程线程的区别_为什么 Java 坚持多线程不选择协程?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java post 打开新页面_JAVA
- 下一篇: java调用matlab 数组_JAVA