jvm線程&&Linux線程&&協(xié)程

1. jvm線程

JDK1.2之前落竹,程序員們?yōu)镴VM開發(fā)了自己的一個(gè)線程調(diào)度內(nèi)核,而到操作系統(tǒng)層面就是用戶空間內(nèi)的線程實(shí)現(xiàn)。而到了JDK1.2及以后胃榕,JVM選擇了更加穩(wěn)健且方便使用的操作系統(tǒng)原生的線程模型淌喻,通過系統(tǒng)調(diào)用僧家,將程序的線程交給了操作系統(tǒng)內(nèi)核進(jìn)行調(diào)度
對(duì)于JDK來說,Linux版都是使用一對(duì)一的線程模型實(shí)現(xiàn)的裸删,一條Java線程就映射到一條輕量級(jí)進(jìn)程(LWP)之中八拱,N對(duì)M的線程模型稱之為協(xié)程(golong有實(shí)現(xiàn))

  • 用戶級(jí)實(shí)現(xiàn)線程:
    程序員需要自己實(shí)現(xiàn)線程的數(shù)據(jù)結(jié)構(gòu)、創(chuàng)建銷毀和調(diào)度維護(hù)涯塔。也就相當(dāng)于需要實(shí)現(xiàn)一個(gè)自己的線程調(diào)度內(nèi)核肌稻,而同時(shí)這些線程運(yùn)行在操作系統(tǒng)的一個(gè)進(jìn)程內(nèi),最后操作系統(tǒng)直接對(duì)進(jìn)程進(jìn)行調(diào)度匕荸,線程的調(diào)度只是在用戶態(tài)爹谭,減少了操作系統(tǒng)從內(nèi)核態(tài)到用戶態(tài)的切換開銷,但是某一個(gè)線程進(jìn)行系統(tǒng)調(diào)用時(shí)一個(gè)線程的阻塞會(huì)導(dǎo)致整個(gè)進(jìn)程阻塞榛搔,用戶態(tài)沒有時(shí)鐘中斷機(jī)制诺凡,也就是說一個(gè)線程長(zhǎng)時(shí)間不是放cpu齿风,會(huì)導(dǎo)致該進(jìn)程中其它線程無法獲取cpu時(shí)間片而持續(xù)等待
    用戶級(jí)線程則不能享受多處理器, 因?yàn)槎鄠€(gè)用戶級(jí)線程對(duì)應(yīng)到一個(gè)內(nèi)核級(jí)線程上, 一個(gè)內(nèi)核級(jí)線程在同一時(shí)刻只能運(yùn)行在一個(gè)處理器上. 不過, M:N的線程模型畢竟提供了這樣一種手段, 可以讓不需要并行執(zhí)行的線程運(yùn)行在一個(gè)內(nèi)核級(jí)線程對(duì)應(yīng)的若干個(gè)用戶級(jí)線程上, 可以節(jié)省它們的切換開銷,用戶級(jí)線程的切換顯然要比內(nèi)核級(jí)線程的切換快一些, 前者可能只是一個(gè)簡(jiǎn)單的長(zhǎng)跳轉(zhuǎn), 而后者則需要保存/裝載寄存器, 進(jìn)入然后退出內(nèi)核態(tài). (進(jìn)程切換則還需要切換地址空間等.) 進(jìn)行內(nèi)核態(tài)和用戶態(tài)的轉(zhuǎn)換
  • 混合實(shí)現(xiàn)M:N模型(用戶線程:LWP不是1:1關(guān)系)
    用戶態(tài)實(shí)現(xiàn)線程和LWP同時(shí)存在绑洛,使用內(nèi)核提供的線程調(diào)度功能及處理器映射救斑,用戶線程的系統(tǒng)調(diào)用要通過輕量級(jí)進(jìn)程來完成,大大降低了整個(gè)進(jìn)程被完全阻塞的風(fēng)險(xiǎn)
    linux上真屯,一個(gè)線程默認(rèn)的棧大小是8M脸候,創(chuàng)建幾萬個(gè)線程就壓力山大,所以會(huì)出現(xiàn)協(xié)程 golang默認(rèn)大小2k
    pthread_create()創(chuàng)建線程時(shí)绑蔫,若不指定分配堆棧大小运沦,系統(tǒng)會(huì)分配默認(rèn)值
    不指定-Xss jvm給Java棧定義的"系統(tǒng)默認(rèn)"大小是1MB,jvm使用linux默認(rèn)的棧大小
$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 515488
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 65535
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 4096
virtual memory          (kbytes, -v) unlimited

協(xié)程:使用棧內(nèi)存是按需使用的配深,所以可以隨隨便便創(chuàng)建百萬級(jí)的協(xié)程携添。而這些協(xié)程本質(zhì)上還是要依托于具體的操作系統(tǒng)線程去執(zhí)行的。比如說我創(chuàng)建了M個(gè)協(xié)程篓叶,然后在N個(gè)線程上執(zhí)行烈掠,這就是M:N的方案,顯然缸托,Java里是沒有協(xié)程的左敌。當(dāng)然,現(xiàn)在OpenJDK社區(qū)的 loom 項(xiàng)目正在努力為JDK增加協(xié)程俐镐,協(xié)程的切換只有cpu上下文切換開銷的矫限,在用戶態(tài)完成,不需要進(jìn)行特權(quán)切換(用戶態(tài)->內(nèi)核態(tài))佩抹,只是恢復(fù)幾個(gè)寄存器的數(shù)據(jù)叼风,而線程切換除了cpu上下文切換開銷,還要進(jìn)行系統(tǒng)調(diào)用執(zhí)行軟中斷棍苹,此時(shí)要進(jìn)行特權(quán)切換(因?yàn)榫€程調(diào)度是由內(nèi)核來完成的无宿,所以需要進(jìn)入內(nèi)核態(tài)),內(nèi)核態(tài)和用戶態(tài)的切換開銷就很大了

  • Java中線程的本質(zhì):
    其實(shí)就是操作系統(tǒng)中的線程(LWP廊勃,對(duì)Linux操作系統(tǒng)來說本質(zhì)上還是進(jìn)程)懈贺,Linux下是基于pthread庫(kù)實(shí)現(xiàn)的輕量級(jí)進(jìn)程(NPTL Native POSIX Thread Library)
  • LWP:LWP是通過clone創(chuàng)建的進(jìn)程,由于LWP和父進(jìn)程會(huì)共享部分資源坡垫,比如地址空間梭灿,文件系統(tǒng),文件句柄冰悠,信號(hào)處理函數(shù)等堡妒,所以把LWP稱為輕量級(jí)進(jìn)程。
    JVM中的線程生命周期

    這些線程的狀態(tài)時(shí)JVM中的線程狀態(tài)和操作系統(tǒng)中的線程狀態(tài)有映射關(guān)系

2. 操作系統(tǒng)中線程和Java線程狀態(tài)的關(guān)系:

從實(shí)際意義上來講溉卓,操作系統(tǒng)中的線程除去newterminated狀態(tài)皮迟,一個(gè)線程真實(shí)存在的狀態(tài)搬泥,只有:
ready:線程等待系統(tǒng)調(diào)度分配CPU使用權(quán)。
running:表示線程獲得了CPU使用權(quán)伏尼,正在進(jìn)行運(yùn)算
waiting:表示線程等待(或者說掛起)忿檩,讓出CPU資源給其他線程使用,運(yùn)行狀態(tài)下的線程如果調(diào)用阻塞 API爆阶,如阻塞方式讀取文件燥透, 線程狀態(tài)就將變成休眠狀態(tài)。這種情況下辨图,線程將會(huì)讓出 CPU 使用權(quán)班套。休眠結(jié)束,線程狀態(tài)將會(huì)先變成可運(yùn)行狀態(tài)故河。
為什么除去newterminated狀態(tài)吱韭?是因?yàn)檫@兩種狀態(tài)實(shí)際上并不存在于線程運(yùn)行中,所以也沒什么實(shí)際討論的意義鱼的。
**對(duì)于Java中的線程狀態(tài):
無論是Timed Waiting 理盆,Waiting還是Blocked,對(duì)應(yīng)的都是操作系統(tǒng)線程的waiting(等待)狀態(tài)鸳吸。
而Runnable狀態(tài)熏挎,則對(duì)應(yīng)了操作系統(tǒng)中的ready和running狀態(tài)速勇。

3. Linux的NPTL庫(kù)實(shí)現(xiàn)了POSIX標(biāo)準(zhǔn)

1: 查看進(jìn)程列表的時(shí)候, 相關(guān)的一組task_struct應(yīng)當(dāng)被展現(xiàn)為列表中的一個(gè)節(jié)點(diǎn);
2: 發(fā)送給這個(gè)"進(jìn)程"的信號(hào)(對(duì)應(yīng)kill系統(tǒng)調(diào)用), 將被對(duì)應(yīng)的這一組task_struct所共享, 并且被其中的任意一個(gè)"線程"處理;
3: 發(fā)送給某個(gè)"線程"的信號(hào)(對(duì)應(yīng)pthread_kill), 將只被對(duì)應(yīng)的一個(gè)task_struct接收, 并且由它自己來處理;
4: 當(dāng)"進(jìn)程"被停止或繼續(xù)時(shí)(對(duì)應(yīng)SIGSTOP/SIGCONT信號(hào)), 對(duì)應(yīng)的這一組task_struct狀態(tài)將改變;
5: 當(dāng)"進(jìn)程"收到一個(gè)致命信號(hào)(比如由于段錯(cuò)誤收到SIGSEGV信號(hào)), 對(duì)應(yīng)的這一組task_struct將全部退出;

NPTL是Linux 線程庫(kù)的一個(gè)新實(shí)現(xiàn)晌砾,線程組其實(shí)是在task_struct中增加了tgid(thread group id)字段,一般認(rèn)為L(zhǎng)inux通過這種方式支持了線程烦磁,其中進(jìn)程的tgid等于自己的pid养匈,線程的tgid等于進(jìn)程的pid。
Linux 進(jìn)程(線程組)是共享虛擬內(nèi)存的

  • 用戶態(tài)pthread_create出來的線程都伪,在內(nèi)核態(tài)呕乎,也擁有自己的進(jìn)程描述符task_struct(copy_process里面調(diào)用dup_task_struct創(chuàng)建)。這是什么意思呢陨晶。意思是我們用戶態(tài)所說的線程猬仁,一樣是內(nèi)核進(jìn)程調(diào)度的實(shí)體。進(jìn)程調(diào)度先誉,嚴(yán)格意義上說應(yīng)該叫LWP調(diào)度湿刽,進(jìn)程調(diào)度,不是以線程組為單位調(diào)度的褐耳,本質(zhì)是以LWP為單位調(diào)度的诈闺。這個(gè)結(jié)論乍一看驚世駭俗,細(xì)細(xì)一想铃芦,其是很合理雅镊。我們?yōu)槭裁炊嗑€程襟雷?因?yàn)槎郈PU,多核仁烹,我們要充分利用多核,同一個(gè)線程組的不同LWP是可以同時(shí)跑在不同的CPU之上的耸弄,因?yàn)檫@個(gè)并發(fā),所以我們有線程鎖的設(shè)計(jì)卓缰,這從側(cè)面證明了叙赚,LWP是調(diào)度的實(shí)體單元

代碼

Thread.start()#start0()
/src/share/vm/prims/jvm.cpp

private native void start0();

static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",            "(J)V",       (void *)&JVM_Sleep},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
native_thread = new JavaThread(&thread_entry, sz);

Java層面 | HotSpot VM層面 | 操作系統(tǒng)層面

java.lang.Thread | JavaThread -> OSThread | native thread
src/share/vm/runtime/thread.hpp
src/share/vm/runtime/osThread.hpp
然后平臺(tái)相關(guān)的部分各自不同,以Linux為例的話是
src/os/linux/vm/osThread_linux.hpp

public class Thread {  
  private long        eetop; // 實(shí)為指向JavaThread的指針  
}
thread.hpp  
class JavaThread: public Thread {}; 
class Thread: public ThreadShadow {  
  // 指向OSThread的指針  
  OSThread* _osthread;  // Platform-specific thread information  
};  
osThread.hpp
class OSThread: public CHeapObj<mtThread> 
osThread_linux.hpp
pthread_t _pthread_id;
  • 抽取代碼可以看出這幾個(gè)數(shù)據(jù)結(jié)構(gòu)的關(guān)系是:
    java.lang.Thread thread;
    JavaThread* jthread = thread->_eetop;
    OSThread* osthread = jthread->_osthread;
    pthread_t pthread_id = osthread->_pthread_id;
  • 內(nèi)核view和用戶view thread
    image.png

    ps -ef
    ps -efL 可以查看lwp 的 pid tid tgid信息僚饭,查看一個(gè)進(jìn)程多少個(gè)線程
    注意ps默認(rèn)只打印進(jìn)程級(jí)別信息震叮,需要用-L選項(xiàng)來查看線程基本信息。

參考
https://blog.csdn.net/CringKong/article/details/79994511
https://blog.csdn.net/mm_hh/java/article/details/72587207
https://www.zhihu.com/question/263955521
http://blog.chinaunix.net/uid-24774106-id-3650136.html
為什么協(xié)程切換的代價(jià)比線程切換低?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鳍鸵,一起剝皮案震驚了整個(gè)濱河市苇瓣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌偿乖,老刑警劉巖击罪,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贪薪,居然都是意外死亡媳禁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門画切,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竣稽,“玉大人,你說我怎么就攤上這事霍弹『帘穑” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵典格,是天一觀的道長(zhǎng)岛宦。 經(jīng)常有香客問我,道長(zhǎng)耍缴,這世上最難降的妖魔是什么砾肺? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮防嗡,結(jié)果婚禮上变汪,老公的妹妹穿的比我還像新娘。我一直安慰自己本鸣,他們只是感情好疫衩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般闷煤。 火紅的嫁衣襯著肌膚如雪童芹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天鲤拿,我揣著相機(jī)與錄音假褪,去河邊找鬼。 笑死近顷,一個(gè)胖子當(dāng)著我的面吹牛生音,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窒升,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼缀遍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了饱须?” 一聲冷哼從身側(cè)響起域醇,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蓉媳,沒想到半個(gè)月后譬挚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酪呻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年减宣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玩荠。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漆腌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姨蟋,到底是詐尸還是另有隱情屉凯,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布眼溶,位于F島的核電站,受9級(jí)特大地震影響晓勇,放射性物質(zhì)發(fā)生泄漏堂飞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一绑咱、第九天 我趴在偏房一處隱蔽的房頂上張望绰筛。 院中可真熱鬧,春花似錦描融、人聲如沸铝噩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骏庸。三九已至毛甲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間具被,已是汗流浹背玻募。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留一姿,地道東北人七咧。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像叮叹,于是被迫代替她去往敵國(guó)和親艾栋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348