日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

java睡眠后继续执行_Java线程只能有千个,而Go的Goroutine能有上百万个

發(fā)布時間:2024/9/30 java 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java睡眠后继续执行_Java线程只能有千个,而Go的Goroutine能有上百万个 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
前言

哈嘍,大家好,我是asong,我又來做知識分享了。

對于做過Java開發(fā)的程序員來說,或許會遇到這個問題:java.lang.OutOfMemoryError: Unable to create new native thread。造成這個問題的原因是因?yàn)門hread限制導(dǎo)致內(nèi)存溢出。對于這個問題,我們可以寫一個小demo,測試一下這個問題:

/** * 功能:Unable to create new native thread * 訂閱號:Golang夢工廠 * create by asong on 2020.6.14 * email:741896420@qq.com */public class main {public static void main(String[] args) {while (true){new Thread(new Runnable() {@Override ? ? ? ? ? ? ? ?public void run() {try {
Thread.sleep(10000000); ? ? ? ? ? ? ? ? ? ?} catch(InterruptedException e) { }
}
}).start(); ? ? ? ?}
}
}

運(yùn)行這段代碼就會發(fā)生錯誤,在我的電腦上在創(chuàng)建了13000個thread的時候發(fā)生了錯誤。但是我們?nèi)绻褂肎o語言嘗試,創(chuàng)建一個Goroutine,并且讓他永久的Sleep,Go語言大概可以創(chuàng)建6千萬個Groutine。我們可以看到Goroutine可以比thread創(chuàng)建多這么多。這一文,我們就來解析這個問題!!!

什么是線程

Thread可以由以下內(nèi)容組成:

1. 一系列按照線性順序可以執(zhí)行的指令(operations);

2. 一個邏輯上可以執(zhí)行的路徑。CPUs中的每一個Core個數(shù)在同一時刻只能真正并發(fā)執(zhí)行一個Logic thread。

如果你的threads個數(shù)大于CPU的Core個數(shù),有一部分的Threads就必須要暫停來讓其他Threads工作,直到這些threads到達(dá)一定的時機(jī)才會被恢復(fù)繼續(xù)執(zhí)行。而暫停和恢復(fù)一個線程,至少需要記錄兩件事情:

1. 當(dāng)前執(zhí)行的指令位置。亦稱為:說當(dāng)前線程被暫停時,線程正在執(zhí)行的代碼行;

2.?還需要一個棧空間。 亦可認(rèn)為:這個棧空間保存了當(dāng)前線程的狀態(tài)。一個棧包含了 local 變量也就是一些指針指向堆內(nèi)存的變量(這個是對于 Java 來說的,對于 C/C++ 可以存儲非指針)。一個進(jìn)程里面所有的 threads 是共享一個堆內(nèi)存的。

記錄了這兩件事情,CPU在調(diào)度thread的時候,就有了足夠的信息,可以暫停一個thread,調(diào)度其他thread運(yùn)行,然后再將暫停的thread恢復(fù),從而繼續(xù)執(zhí)行。這些操作對于thread來說通常是完全透明的。從thread的角度去看,他一直都在連續(xù)的運(yùn)行著。thread被取消調(diào)度這樣的行為可以被觀察的唯一辦法就是測量后續(xù)操作的時間。

jvm使用的操作系統(tǒng)

盡管規(guī)范沒有要求所有現(xiàn)代的通用 JVM,在我所知道的范圍內(nèi),當(dāng)前市面上所有的現(xiàn)代通用目的的 JVM 中的 thread 都是被設(shè)計(jì)成為了操作系統(tǒng)的thread。下面,我將使用“用戶空間 threads" 的概念來指代被語言來調(diào)度而不是被操作系統(tǒng)內(nèi)核調(diào)度的 threads。操作系統(tǒng)級別實(shí)現(xiàn)的 threads 主要有如下兩點(diǎn)限制:首先限制了 threads 的總數(shù)量,其次對于語言層面的 thread 和操作系統(tǒng)層面的 thread 進(jìn)行 1:1 映射的場景,沒有支持海量并發(fā)的解決方案。

jvm中固定大小的棧

使用操作系統(tǒng)層面的 thread,每一個 thread 都需要耗費(fèi)靜態(tài)的大量的內(nèi)存 第二個使用操作系統(tǒng)層面的 thread 所帶來的問題是,每一個 thread 都需要一個固定的棧內(nèi)存。雖然這個內(nèi)存大小是可以配置的,但在 64 位的 JVM 環(huán)境中,一個 thread 默認(rèn)使用1MB的棧內(nèi)存。雖然你可以將默認(rèn)的棧內(nèi)存大小改小一點(diǎn),但是您會權(quán)衡內(nèi)存使用情況, 從而增加堆棧溢出的風(fēng)險。在你的代碼中遞歸次數(shù)越大,越有可能觸發(fā)棧溢出。如果使用1MB的棧默認(rèn)值,那么創(chuàng)建1000個 threads ,將使用 1GB 的 RAM ,雖然 RAM 現(xiàn)在很便宜,但是如果要創(chuàng)建一億個 threads ,就需要T級別的內(nèi)存。

Golang處理辦法:動態(tài)大小的棧

Go 語言為了避免是使用過大的棧內(nèi)存(大部分都是未使用的)導(dǎo)致內(nèi)存溢出,使用了一個非常聰明的技巧:Go 的棧大小是動態(tài)的,隨著存儲的數(shù)據(jù)大小增長和收縮。這不是一件簡單微小的事情,這個特性經(jīng)過了好幾個版本的迭代開發(fā)[4]。很多其他人的關(guān)于 Go 語言的文章中都已經(jīng)做了詳細(xì)的說明,本文不打算在這里討論內(nèi)部的細(xì)節(jié)。結(jié)果就是新建的一個 Goroutine 實(shí)際只占用 4KB 的棧空間。一個棧只占用 4KB,1GB 的內(nèi)存可以創(chuàng)建 250 萬個 Goroutine,相對于 Java 一個棧占用 1MB 的內(nèi)存,這的確是一個很大的提高。

JVM中上下文切換的延遲

使用操作系統(tǒng)的 threads 的最大能力一般在萬級別,主要消耗是在上下文切換的延遲。

因?yàn)?JVM 是使用操作系統(tǒng)的 threads ,也就是說是由操作系統(tǒng)內(nèi)核進(jìn)行 threads 的調(diào)度。操作系統(tǒng)本身有一個所有正在運(yùn)行的進(jìn)程和線程的列表,同時操作系統(tǒng)給它們中的每一個都分配一個“公平”的使用 CPU 的時間片。當(dāng)內(nèi)核從一個 thread 切換到另外一個時候,它其實(shí)有很多事情需要去做。

新線程或進(jìn)程的運(yùn)行必須以世界的視角開始,它可以抽象出其他線程在同一 CPU 上運(yùn)行的事實(shí)。這個問題的關(guān)鍵點(diǎn)是上下文的切換大概需要消耗 1-100μ 秒。這個看上去好像不是很耗時,但是在現(xiàn)實(shí)中每次平均切換需要消耗10μ秒,如果想讓在一秒鐘內(nèi),所有的 threads 都能被調(diào)用到,那么 threads 在一個 core 上最多只能有 10 萬個 threads,而事實(shí)上這些 threads 自身已經(jīng)沒有任何時間去做自己的有意義的工作了。

Go 的行為有何不同:在一個操作系統(tǒng)線程上運(yùn)行多個 Goroutines

Golang 語言本身有自己的調(diào)度策略,允許多個 Goroutines 運(yùn)行在一個同樣的 OS thread 上。既然 Golang 能像內(nèi)核一樣運(yùn)行代碼的上下文切換,這樣它就能省下大量的時間來避免從用戶態(tài)切換到 ring-0 的內(nèi)核態(tài)再切換回來的過程。但是這只是表面上能看到的,事實(shí)上為 Go 語言支持 100 萬的 goroutines,Go 語言其實(shí)還做了更多更復(fù)雜的事情。

即使 JVM 把 threads 帶到了用戶空間,它依然無法支持百萬級別的 threads ,想象下在你的新的系統(tǒng)中,在 thread 間進(jìn)行切換只需要耗費(fèi)100 納秒,即使只做上下文切換,有也只能使 100 萬個 threads 每秒鐘做 10 次上下文的切換,更重要的是,你必須要讓你的 CPU 滿負(fù)荷的做這樣的事情。

支持真正的高并發(fā)需要另外一種優(yōu)化思路:當(dāng)你知道這個線程能做有用的工作的時候,才去調(diào)度這個線程!如果你正在運(yùn)行多線程,其實(shí)無論何時,只有少部分的線程在做有用的工作。Go 語言引入了 channel 的機(jī)制來協(xié)助這種調(diào)度機(jī)制。如果一個 goroutine 正在一個空的 channel 上等待,那么調(diào)度器就能看到這些,并不再運(yùn)行這個 goroutine 。同時 Go 語言更進(jìn)了一步。它把很多個大部分時間空閑的 goroutines 合并到了一個自己的操作系統(tǒng)線程上。這樣可以通過一個線程來調(diào)度活動的 Goroutine(這個數(shù)量小得多),而是數(shù)百萬大部分狀態(tài)處于睡眠的 goroutines 被分離出來。這種機(jī)制也有助于降低延遲。

除非 Java 增加一些語言特性來支持調(diào)度可見的功能,否則支持智能調(diào)度是不可能實(shí)現(xiàn)的。但是你可以自己在“用戶態(tài)”構(gòu)建一個運(yùn)行時的調(diào)度器,來調(diào)度何時線程可以工作。其實(shí)這就是構(gòu)成Akka這種數(shù)百萬 actors 并發(fā)框架的基礎(chǔ)概念。

總結(jié)

未來,會有越來越多的從操作系統(tǒng)層面的 thread 模型向輕量級的用戶空間級別的 threads 模型遷移發(fā)生。從使用角度看,使用高級的并發(fā)特性是必須的,也是唯一的需求。這種需求其實(shí)并沒有增加過多的的復(fù)雜度。如果 Go 語言改用操作系統(tǒng)級別的 threads 來替代目前現(xiàn)有的調(diào)度和棧空間自增長的機(jī)制,其實(shí)也就是在 runtime 的代碼包中減少數(shù)千行的代碼。但對于大多數(shù)的用戶案例上考慮,這是一個更好的的模式。復(fù)雜度被語言庫的作者做了很好的抽象,這樣軟件工程師就可以寫出高并發(fā)的程序了。

今日的分享到此結(jié)束了。感謝各位的觀看,如果有錯誤歡迎指出。

歡迎關(guān)注公眾號:Golang夢工廠,后臺回復(fù)Gin獲取2020最新版本Gin中文文檔。

訂閱號:Golang夢工廠

-掃碼關(guān)注公眾號后臺回復(fù)gin

?獲取2020最新版本Gin中文文

?檔-

總結(jié)

以上是生活随笔為你收集整理的java睡眠后继续执行_Java线程只能有千个,而Go的Goroutine能有上百万个的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。