Java線程和操作系統(tǒng)線程的關(guān)系

傳統(tǒng)進(jìn)程的缺點(diǎn)

  • fork一個(gè)子進(jìn)程的消耗是很大的,fork是一個(gè)昂貴的系統(tǒng)調(diào)用鞍泉,即使使用現(xiàn)代的寫時(shí)復(fù)制(copy-on-write)技術(shù)实抡。
  • 各個(gè)進(jìn)程擁有自己獨(dú)立的地址空間,進(jìn)程間的協(xié)作需要復(fù)雜的IPC技術(shù)苏揣,如消息傳遞和共享內(nèi)存等。

多線程的優(yōu)缺點(diǎn)
線程:其實(shí)可以先簡(jiǎn)單理解成cpu的一個(gè)執(zhí)行流推姻,指令序列平匈。多支持多線程的程序(進(jìn)程)可以取得真正的并行(parallelism),且由于共享進(jìn)程的代碼和全局?jǐn)?shù)據(jù)藏古,故線程間的通信是方便的增炭。它的缺點(diǎn)也是由于線程共享進(jìn)程的地址空間,因此可能會(huì)導(dǎo)致競(jìng)爭(zhēng)拧晕,因此對(duì)某一塊有多個(gè)線程要訪問的數(shù)據(jù)需要一些同步技術(shù)隙姿。
輕量級(jí)進(jìn)程LWP
既然稱作輕量級(jí)進(jìn)程,可見其本質(zhì)仍然是進(jìn)程厂捞,與普通進(jìn)程相比输玷,LWP與其它進(jìn)程共享所有(或大部分)邏輯地址空間和系統(tǒng)資源,一個(gè)進(jìn)程可以創(chuàng)建多個(gè)LWP靡馁,這樣它們共享大部分資源欲鹏;LWP有它自己的進(jìn)程標(biāo)識(shí)符,并和其他進(jìn)程有著父子關(guān)系臭墨,父進(jìn)程)赔嚎;這是和類Unix操作系統(tǒng)的系統(tǒng)調(diào)用vfork()生成的進(jìn)程一樣的。
LWP由內(nèi)核管理并像普通進(jìn)程一樣被調(diào)度。Linux內(nèi)核是支持LWP的典型例子尤误。Linux內(nèi)核在 2.0.x版本就已經(jīng)實(shí)現(xiàn)了輕量進(jìn)程侠畔,應(yīng)用程序可以通過一個(gè)統(tǒng)一的clone()系統(tǒng)調(diào)用接口,用不同的參數(shù)指定創(chuàng)建輕量進(jìn)程還是普通進(jìn)程袄膏,通過參數(shù)決定子進(jìn)程和父進(jìn)程共享的資源種類和數(shù)量践图,這樣就有了輕重之分。在內(nèi)核中沉馆, clone()調(diào)用經(jīng)過參數(shù)傳遞和解釋后會(huì)調(diào)用do_fork()码党,這個(gè)核內(nèi)函數(shù)同時(shí)也是fork()、vfork()系統(tǒng)調(diào)用的最終實(shí)現(xiàn)斥黑。

在大多數(shù)系統(tǒng)中揖盘,LWP與普通進(jìn)程的區(qū)別也在于它只有一個(gè)最小的執(zhí)行上下文和調(diào)度程序所需的統(tǒng)計(jì)信息,而這也是它之所以被稱為輕量級(jí)的原因锌奴。
因?yàn)長(zhǎng)WP之間共享它們的大部分資源兽狭,所以它在某些應(yīng)用程序就不適用了;這個(gè)時(shí)候就要使用多個(gè)普通的進(jìn)程了

用戶線程
用戶線程指的是完全建立在用戶空間的線程庫鹿蜀,用戶線程的建立箕慧,同步,銷毀茴恰,調(diào)度完全在用戶空間完成颠焦,不需要內(nèi)核的幫助。因此這種線程的操作是極其快速的且低消耗的往枣。

操作系統(tǒng)線程模型

線程實(shí)現(xiàn)在用戶空間下

當(dāng)線程在用戶空間下實(shí)現(xiàn)時(shí)伐庭,操作系統(tǒng)對(duì)線程的存在一無所知,操作系統(tǒng)只能看到進(jìn)程分冈,而不能看到線程圾另。所有的線程都是在用戶空間實(shí)現(xiàn)。在操作系統(tǒng)看來雕沉,每一個(gè)進(jìn)程只有一個(gè)線程集乔。過去的操作系統(tǒng)大部分是這種實(shí)現(xiàn)方式淮韭,這種方式的好處之一就是即使操作系統(tǒng)不支持線程斩启,也可以通過庫函數(shù)來支持線程澜倦。

我們換一種通俗的方式來講解這段話胰伍,首先就是在這在模型下,程序員需要自己實(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)度。

image.png

這樣做有一些優(yōu)點(diǎn)梢睛,就是線程的調(diào)度只是在用戶態(tài)肥印,減少了操作系統(tǒng)從內(nèi)核態(tài)到用戶態(tài)的切換開銷识椰。
當(dāng)然缺點(diǎn)也很明顯:這種模式最致命的缺點(diǎn)也是由于操作系統(tǒng)不知道線程的存在,因此當(dāng)一個(gè)進(jìn)程中的某一個(gè)線程進(jìn)行系統(tǒng)調(diào)用時(shí)深碱,比如缺頁中斷而導(dǎo)致線程阻塞腹鹉,此時(shí)操作系統(tǒng)會(huì)阻塞整個(gè)進(jìn)程,即使這個(gè)進(jìn)程中其它線程還在工作

線程實(shí)現(xiàn)在操作系統(tǒng)內(nèi)核中

內(nèi)核線程就是直接由操作系統(tǒng)內(nèi)核(Kernel)支持的線程敷硅,這種線程由內(nèi)核來完成線程切換功咒,內(nèi)核通過操縱調(diào)度器(Scheduler)對(duì)線程進(jìn)行調(diào)度,并負(fù)責(zé)將線程的任務(wù)映射到各個(gè)處理器上绞蹦。每個(gè)內(nèi)核線程可以視為內(nèi)核的一個(gè)分身力奋,這樣操作系統(tǒng)就有能力同時(shí)處理多件事情,支持多線程的內(nèi)核就叫做多線程內(nèi)核(Multi-Threads Kernel)幽七。

通俗的講就是景殷,程序員直接使用操作系統(tǒng)中已經(jīng)實(shí)現(xiàn)的線程,而線程的創(chuàng)建澡屡、銷毀猿挚、調(diào)度和維護(hù),都是靠操作系統(tǒng)(準(zhǔn)確的說是內(nèi)核)來實(shí)現(xiàn)驶鹉,程序員只需要使用系統(tǒng)調(diào)用绩蜻,而不需要自己設(shè)計(jì)線程的調(diào)度算法和線程對(duì)CPU資源的搶占使用

目前的Linux已經(jīng)基于NPTL實(shí)現(xiàn)了更符合POSIX標(biāo)準(zhǔn)的線程梁厉,之前的LinuxThreads早已經(jīng)被替代辜羊。Pthread庫目前也是基于NPTL實(shí)現(xiàn),已經(jīng)是一種符合POSIX標(biāo)準(zhǔn)的線程模型了词顾,Linux擺脫了原來LWP的陰影八秃。

使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)

在這種混合實(shí)現(xiàn)下,即存在用戶線程肉盹,也存在輕量級(jí)進(jìn)程昔驱。用戶線程還是完全建立在用戶空間中,因此用戶線程的創(chuàng)建上忍、切換骤肛、析構(gòu)等操作依然廉價(jià),并且可以支持大規(guī)模的用戶線程并發(fā)窍蓝。而操作系統(tǒng)提供支持的輕量級(jí)進(jìn)程則作為用戶線程和內(nèi)核線程之間的橋梁腋颠,這樣可以使用內(nèi)核提供的線程調(diào)度功能及處理器映射,并且用戶線程的系統(tǒng)調(diào)用要通過輕量級(jí)進(jìn)程來完成吓笙,大大降低了整個(gè)進(jìn)程被完全阻塞的風(fēng)險(xiǎn)淑玫。在這種混合模式中,用戶線程與輕量級(jí)進(jìn)程的數(shù)量比是不定的,即為N:M的關(guān)系:

Linux線程的發(fā)展

一直以來, linux內(nèi)核并沒有線程的概念. 每一個(gè)執(zhí)行實(shí)體都是一個(gè)task_struct結(jié)構(gòu), 通常稱之為進(jìn)程. Linux內(nèi)核在 2.0.x版本就已經(jīng)實(shí)現(xiàn)了輕量進(jìn)程絮蒿,應(yīng)用程序可以通過一個(gè)統(tǒng)一的clone()系統(tǒng)調(diào)用接口尊搬,用不同的參數(shù)指定創(chuàng)建輕量進(jìn)程還是普通進(jìn)程。在內(nèi)核中土涝, clone()調(diào)用經(jīng)過參數(shù)傳遞和解釋后會(huì)調(diào)用do_fork()佛寿,這個(gè)核內(nèi)函數(shù)同時(shí)也是fork()、vfork()系統(tǒng)調(diào)用的最終實(shí)現(xiàn)但壮。后來為了引入多線程冀泻,Linux2.0~2.4實(shí)現(xiàn)的是俗稱LinuxThreads的多線程方式,到了2.6茵肃,基本上都是NPTL的方式了

模型一 :LinuxThreads

linux 2.6以前, pthread線程庫對(duì)應(yīng)的實(shí)現(xiàn)是一個(gè)名叫l(wèi)inuxthreads的lib.這種實(shí)現(xiàn)本質(zhì)上是一種LWP的實(shí)現(xiàn)方式腔长,即通過輕量級(jí)進(jìn)程來模擬線程,內(nèi)核并不知道有線程這個(gè)概念验残,在內(nèi)核看來捞附,都是進(jìn)程。

Linux采用的“一對(duì)一”的線程模型您没,即一個(gè)LWP對(duì)應(yīng)一個(gè)線程鸟召。這個(gè)模型最大的好處是線程調(diào)度由內(nèi)核完成了,而其他線程操作(同步氨鹏、取消)等都是核外的線程庫函數(shù)完成的欧募。

linux上的線程就是基于輕量級(jí)進(jìn)程, 由用戶態(tài)的pthread庫實(shí)現(xiàn)的.使用pthread以后, 在用戶看來, 每一個(gè)task_struct就對(duì)應(yīng)一個(gè)線程, 而一組線程以及它們所共同引用的一組資源就是一個(gè)進(jìn)程.但是, 一組線程并不僅僅是引用同一組資源就夠了, 它們還必須被視為一個(gè)整體.

對(duì)此, POSIX(portable operation system interface)標(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將全部退出;
6, 等等(以上可能不夠全);

在LinuxThreads中,專門為每一個(gè)進(jìn)程構(gòu)造了一個(gè)管理線程仆抵,負(fù)責(zé)處理線程相關(guān)的管理工作跟继。當(dāng)進(jìn)程第一次調(diào)用pthread_create()創(chuàng)建一個(gè)線程的時(shí)候就會(huì)創(chuàng)建并啟動(dòng)管理線程。然后管理線程再來創(chuàng)建用戶請(qǐng)求的線程镣丑。也就是說舔糖,用戶在調(diào)用pthread_create后,先是創(chuàng)建了管理線程莺匠,再由管理線程創(chuàng)建了用戶的線程金吗。

linuxthreads利用前面提到的輕量級(jí)進(jìn)程來實(shí)現(xiàn)線程, 但是對(duì)于POSIX提出的那些要求,linuxthreads除了第5點(diǎn)以外, 都沒有實(shí)現(xiàn)(實(shí)際上是無能為力):

1, 如果運(yùn)行了A程序, A程序創(chuàng)建了10個(gè)線程, 那么在shell下執(zhí)行ps命令時(shí)將看到11個(gè)A進(jìn)程, 而不是1個(gè)(注意, 也不是10個(gè), 下面會(huì)解釋);

2, 不管是kill還是pthread_kill, 信號(hào)只能被一個(gè)對(duì)應(yīng)的線程所接收;

3, SIGSTOP/SIGCONT信號(hào)只對(duì)一個(gè)線程起作用;

還好linuxthreads實(shí)現(xiàn)了第5點(diǎn), 我認(rèn)為這一點(diǎn)是最重要的. 如果某個(gè)線程"掛"了, 整個(gè)進(jìn)程還在若無其事地運(yùn)行著, 可能會(huì)出現(xiàn)很多的不一致狀態(tài). 進(jìn)程將不是一個(gè)整體, 而線程也不能稱為線程. 或許這也是為什么linuxthreads雖然與POSIX的要求差距甚遠(yuǎn), 卻能夠存在, 并且還被使用了好幾年的原因吧~

但是, linuxthreads為了實(shí)現(xiàn)這個(gè)"第5點(diǎn)", 還是付出了很多代價(jià), 并且創(chuàng)造了linuxthreads本身的一大性能瓶頸.

為什么A程序創(chuàng)建了10個(gè)線程, 但是ps時(shí)卻會(huì)出現(xiàn)11個(gè)A進(jìn)程了. 因?yàn)閘inuxthreads自動(dòng)創(chuàng)建了一個(gè)管理線程. 上面提到的"第5點(diǎn)"就是靠管理線程來實(shí)現(xiàn)的.

當(dāng)程序開始運(yùn)行時(shí), 并沒有管理線程存在(因?yàn)楸M管程序已經(jīng)鏈接了pthread庫, 但是未必會(huì)使用多線程).

程序第一次調(diào)用pthread_create時(shí), linuxthreads發(fā)現(xiàn)管理線程不存在, 于是創(chuàng)建這個(gè)管理線程. 這個(gè)管理線程是進(jìn)程中的第一個(gè)線程(主線程)的兒子.
然后在pthread_create中, 會(huì)通過pipe向管理線程發(fā)送一個(gè)命令, 告訴它創(chuàng)建線程. 即是說, 除主線程外, 所有的線程都是由管理線程來創(chuàng)建的, 管理線程是它們的父親.
于是, 當(dāng)任何一個(gè)子線程退出時(shí), 管理線程將收到SIGUSER1信號(hào)(這是在通過clone創(chuàng)建子線程時(shí)指定的). 管理線程在對(duì)應(yīng)的sig_handler中會(huì)判斷子線程是否正常退出, 如果不是, 則殺死所有線程, 然后自殺.

那么, 主線程怎么辦呢? 主線程是管理線程的父親, 其退出時(shí)并不會(huì)給管理線程發(fā)信號(hào). 于是, 在管理線程的主循環(huán)中通過getppid檢查父進(jìn)程的ID號(hào), 如果ID號(hào)是1, 說明父親已經(jīng)退出, 并把自己托管給了init進(jìn)程(1號(hào)進(jìn)程). 這時(shí)候, 管理線程也會(huì)殺掉所有子線程, 然后自殺.

可見, 線程的創(chuàng)建與銷毀都是通過管理線程來完成的, 于是管理線程就成了linuxthreads的一個(gè)性能瓶頸.
創(chuàng)建與銷毀需要一次進(jìn)程間通信, 一次上下文切換之后才能被管理線程執(zhí)行, 并且多個(gè)請(qǐng)求會(huì)被管理線程串行地執(zhí)行.

這種通過LWP的方式來模擬線程的實(shí)現(xiàn)看起來還是比較巧妙的,但也存在一些比較嚴(yán)重的問題:

1)線程ID和進(jìn)程ID的問題
按照POSIX的定義趣竣,同一進(jìn)程的所有的線程應(yīng)該共享同一個(gè)進(jìn)程和父進(jìn)程ID摇庙,而Linux的這種LWP方式顯然不能滿足這一點(diǎn)。

2)信號(hào)處理問題

異步信號(hào)是以進(jìn)程為單位分發(fā)的遥缕,而Linux的線程本質(zhì)上每個(gè)都是一個(gè)進(jìn)程卫袒,且沒有進(jìn)程組的概念,所以某些缺省信號(hào)難以做到對(duì)所有線程有效单匣,例如SIGSTOP和SIGCONT夕凝,就無法將整個(gè)進(jìn)程掛起烤蜕,而只能將某個(gè)線程掛起。

3)線程總數(shù)問題
LinuxThreads將每個(gè)進(jìn)程的線程最大數(shù)目定義為1024迹冤,但實(shí)際上這個(gè)數(shù)值還受到整個(gè)系統(tǒng)的總進(jìn)程數(shù)限制,這又是由于線程其實(shí)是核心進(jìn)程虎忌。

4)管理線程問題

管理線程容易成為瓶頸泡徙,這是這種結(jié)構(gòu)的通病膜蠢;同時(shí)堪藐,管理線程又負(fù)責(zé)用戶線程的清理工作,因此挑围,盡管管理線程已經(jīng)屏蔽了大部分的信號(hào)礁竞,但一旦管理線程死亡,用戶線程就不得不手工清理了杉辙,而且用戶線程并不知道管理線程的狀態(tài)模捂,之后的線程創(chuàng)建等請(qǐng)求將無人處理。

5)同步問題
LinuxThreads中的線程同步很大程度上是建立在信號(hào)基礎(chǔ)上的蜘矢,這種通過內(nèi)核復(fù)雜的信號(hào)處理機(jī)制的同步方式狂男,效率一直是個(gè)問題。

模型二:NPTL

到了linux 2.6, glibc中有了一種新的pthread線程庫--NPTL(Native POSIX Threading Library).

本質(zhì)上來說品腹,NPTL還是一個(gè)LWP的實(shí)現(xiàn)機(jī)制岖食,但相對(duì)原有LinuxThreads來說,做了很多的改進(jìn)舞吭。下面我們看一下NPTL如何解決原有LinuxThreads實(shí)現(xiàn)機(jī)制的缺陷

NPTL實(shí)現(xiàn)了前面提到的POSIX的全部5點(diǎn)要求. 但是, 實(shí)際上, 與其說是NPTL實(shí)現(xiàn)了, 不如說是linux內(nèi)核實(shí)現(xiàn)了.

在linux 2.6中, 內(nèi)核有了線程組的概念, task_struct結(jié)構(gòu)中增加了一個(gè)tgid(thread group id)字段.

如果這個(gè)task是一個(gè)"主線程", 則它的tgid等于pid, 否則tgid等于進(jìn)程的pid(即主線程的pid).

在clone系統(tǒng)調(diào)用中, 傳遞CLONE_THREAD參數(shù)就可以把新進(jìn)程的tgid設(shè)置為父進(jìn)程的tgid(否則新進(jìn)程的tgid會(huì)設(shè)為其自身的pid).

類似的XXid在task_struct中還有兩 個(gè):task->signal->pgid保存進(jìn)程組的打頭進(jìn)程的pid泡垃、task->signal->session保存會(huì)話 打頭進(jìn)程的pid。通過這兩個(gè)id來關(guān)聯(lián)進(jìn)程組和會(huì)話羡鸥。

有了tgid, 內(nèi)核或相關(guān)的shell程序就知道某個(gè)tast_struct是代表一個(gè)進(jìn)程還是代表一個(gè)線程, 也就知道在什么時(shí)候該展現(xiàn)它們, 什么時(shí)候不該展現(xiàn)(比如在ps的時(shí)候, 線程就不要展現(xiàn)了).
而getpid(獲取進(jìn)程ID)系統(tǒng)調(diào)用返回的也是tast_struct中的tgid, 而tast_struct中的pid則由gettid系統(tǒng)調(diào)用來返回.

在執(zhí)行ps命令的時(shí)候不展現(xiàn)子線程蔑穴,也是有一些問題的。比如程序a.out運(yùn)行時(shí)兄春,創(chuàng)建 了一個(gè)線程澎剥。假設(shè)主線程的pid是10001、子線程是10002(它們的tgid都是10001)赶舆。這時(shí)如果你kill 10002哑姚,是可以把10001和10002這兩個(gè)線程一起殺死的,盡管執(zhí)行ps命令的時(shí)候根本看不到10002這個(gè)進(jìn)程芜茵。如果你不知道linux線程背 后的故事叙量,肯定會(huì)覺得遇到靈異事件了

為了應(yīng)付"發(fā)送給進(jìn)程的信號(hào)"和"發(fā)送給線程的信號(hào)", task_struct里面維護(hù)了兩套signal_pending, 一套是線程組共享的, 一套是線程獨(dú)有的.

通過kill發(fā)送的信號(hào)被放在線程組共享的signal_pending中, 可以由任意一個(gè)線程來處理; 通過pthread_kill發(fā)送的信號(hào)(pthread_kill是pthread庫的接口, 對(duì)應(yīng)的系統(tǒng)調(diào)用中tkill)被放在線程獨(dú)有的signal_pending中, 只能由本線程來處理.

當(dāng)線程停止/繼續(xù), 或者是收到一個(gè)致命信號(hào)時(shí), 內(nèi)核會(huì)將處理動(dòng)作施加到整個(gè)線程組中

Java線程

Java線程在操作系統(tǒng)上本質(zhì)

Java線程在JDK1.2之前,是基于稱為“綠色線程”(Green Threads)的用戶線程實(shí)現(xiàn)的九串,而在JDK1.2中绞佩,線程模型替換為基于操作系統(tǒng)原生線程模型來實(shí)現(xiàn)寺鸥。因此,在目前的JDK版本中品山,操作系統(tǒng)支持怎樣的線程模型胆建,在很大程度上決定了Java虛擬機(jī)的線程是怎樣映射的,這點(diǎn)在不同的平臺(tái)上沒有辦法達(dá)成一致肘交,虛擬機(jī)規(guī)范中也并未限定Java線程需要使用哪種線程模型來實(shí)現(xiàn)笆载。線程模型只對(duì)線程的并發(fā)規(guī)模和操作成本產(chǎn)生影響,對(duì)Java程序的編碼和運(yùn)行過程來說涯呻,這些差異都是透明的凉驻。
也就說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)度。現(xiàn)在的Java中線程的本質(zhì)填帽,其實(shí)就是操作系統(tǒng)中的線程**

image.png

特別注意:這些線程的狀態(tài)時(shí)JVM中的線程狀態(tài)蛛淋!不是操作系統(tǒng)中的線程狀態(tài)。

操作系統(tǒng)中的進(jìn)程(線程)狀態(tài)**(區(qū)分和JVM中的線程狀態(tài))
image.png

這里需要著重解釋一點(diǎn)篡腌,在現(xiàn)在的操作系統(tǒng)中褐荷,因?yàn)榫€程依舊被視為輕量級(jí)進(jìn)程,所以操作系統(tǒng)中線程的狀態(tài)實(shí)際上和進(jìn)程狀態(tài)是一致的模型嘹悼。

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

從實(shí)際意義上來講叛甫,操作系統(tǒng)中的線程除去new和terminated狀態(tài),一個(gè)線程真實(shí)存在的狀態(tài)杨伙,只有:

  • ready:表示線程已經(jīng)被創(chuàng)建其监,正在等待系統(tǒng)調(diào)度分配CPU使用權(quán)。
  • running:表示線程獲得了CPU使用權(quán)限匣,正在進(jìn)行運(yùn)算
  • waiting:表示線程等待(或者說掛起)抖苦,讓出CPU資源給其他線程使用
    為什么除去new和terminated狀態(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)。
狀態(tài)轉(zhuǎn)換

image.png

Java語言定義了5種線程狀態(tài)物喷,在任意一個(gè)時(shí)間點(diǎn)卤材,一個(gè)線程只能有且只有其中的一種狀態(tài)遮斥,這5種狀態(tài)分別如下:

1)新建(New):創(chuàng)建后尚未啟動(dòng)的線程處于這種狀態(tài)。

2)運(yùn)行(Runable):Runable包括了操作系統(tǒng)線程狀態(tài)中的Running和Ready扇丛,也就是處于此狀態(tài)的線程有可能正在執(zhí)行术吗,也有可能正在等待著CPU為它分配執(zhí)行時(shí)間。

3)無限期等待(Waiting):處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間帆精,它們要等待被其它線程顯示地喚醒藐翎。以下方法會(huì)讓線程陷入無限期的等待狀態(tài):

  • 沒有設(shè)置Timeout參數(shù)的Object.wait()方法
  • 沒有設(shè)置Timeout參數(shù)的Thread.join()方法
  • LockSupport.park()方法
  1. 限期等待(Timed Waiting):處于這種狀態(tài)的線程也不會(huì)被分配CPU執(zhí)行時(shí)間,不過無須等待被其它線程顯示地喚醒实幕,在一定時(shí)間之后它們會(huì)由系統(tǒng)自動(dòng)喚醒。以下方法會(huì)讓線程進(jìn)入限期等待狀態(tài):
  Thread.sleep()方法
  設(shè)置了Timeout參數(shù)的Object.wait()方法
  設(shè)置了Timeout參數(shù)的Thread.join()方法
  LockSupport.parkNanos()方法
  LockSupport.parkUntil()方法

而對(duì)不同的操作系統(tǒng)堤器,由于本身設(shè)計(jì)思路不一樣昆庇,對(duì)于線程的設(shè)計(jì)也存在種種差異,所以JVM在設(shè)計(jì)上闸溃,就已經(jīng)聲明:

虛擬機(jī)中的線程狀態(tài)整吆,不反應(yīng)任何操作系統(tǒng)線程狀態(tài)

java主線程結(jié)束和子線程結(jié)束之間的關(guān)系

Main線程是個(gè)非守護(hù)線程,不能設(shè)置成守護(hù)線程辉川。

這是因?yàn)楸眚琺ain線程是由java虛擬機(jī)在啟動(dòng)的時(shí)候創(chuàng)建的。main方法開始執(zhí)行的時(shí)候乓旗,主線程已經(jīng)創(chuàng)建好并在運(yùn)行了府蛇。對(duì)于運(yùn)行中的線程,調(diào)用Thread.setDaemon()會(huì)拋出異常Exception in thread "main" java.lang.IllegalThreadStateException屿愚。測(cè)試代碼如下:

public class MainTest
{
    public static void main(String[] args)
    {
        System.out.println(" parent thread begin ");
        Thread.currentThread().setDaemon(true);
    }
}
Main線程結(jié)束汇跨,其他線程一樣可以正常運(yùn)行。

主線程妆距,只是個(gè)普通的非守護(hù)線程穷遂,用來啟動(dòng)應(yīng)用程序,不能設(shè)置成守護(hù)線程娱据;除此之外蚪黑,它跟其他非守護(hù)線程沒有什么不同。主線程執(zhí)行結(jié)束中剩,其他線程一樣可以正常執(zhí)行忌穿。代碼如下:

public class ParentTest
{
 
    public static void main(String[] args)
    {
        System.out.println("parent thread begin ");
        
        ChildThread t1 = new ChildThread("thread1");
        ChildThread t2 = new ChildThread("thread2");
        t1.start();
        t2.start();
 
        System.out.println("parent thread over ");
    }
}
 
class ChildThread extends Thread
{
    private String name = null;
 
    public ChildThread(String name)
    {
        this.name = name;
    }
 
    @Override
    public void run()
    {
        System.out.println(this.name + "--child thead begin");
 
        try
        {
            Thread.sleep(500);
        }
        catch (InterruptedException e)
        {
            System.out.println(e);
        }
 
        System.out.println(this.name + "--child thead over");
    }
}

這樣其實(shí)是很合理的,按照操作系統(tǒng)的理論咽安,進(jìn)程是資源分配的基本單位伴网,線程是CPU調(diào)度的基本單位。對(duì)于CPU來說妆棒,其實(shí)并不存在java的主線程和子線程之分澡腾,都只是個(gè)普通的線程沸伏。進(jìn)程的資源是線程共享的,只要進(jìn)程還在动分,線程就可以正常執(zhí)行毅糟,換句話說線程是強(qiáng)依賴于進(jìn)程的。也就是說澜公,線程其實(shí)并不存在互相依賴的關(guān)系姆另,一個(gè)線程的死亡從理論上來說,不會(huì)對(duì)其他線程有什么影響坟乾。

Main線程結(jié)束迹辐,其他線程也可以立刻結(jié)束,當(dāng)且僅當(dāng)這些子線程都是守護(hù)線程甚侣。

java虛擬機(jī)(相當(dāng)于進(jìn)程)退出的時(shí)機(jī)是:虛擬機(jī)中所有存活的線程都是守護(hù)線程明吩。只要還有存活的非守護(hù)線程虛擬機(jī)就不會(huì)退出,而是等待非守護(hù)線程執(zhí)行完畢殷费;反之印荔,如果虛擬機(jī)中的線程都是守護(hù)線程,那么不管這些線程的死活java虛擬機(jī)都會(huì)退出详羡。測(cè)試代碼如下:

public class ParentTest
{
 
    public static void main(String[] args)
    {
        System.out.println("parent thread begin ");
        
        ChildThread t1 = new ChildThread("thread1");
        ChildThread t2 = new ChildThread("thread2");
        t1.setDaemon(true);
        t2.setDaemon(true);
        
        t1.start();
        t2.start();
 
        System.out.println("parent thread over ");
    }
}
class ChildThread extends Thread
{
    private String name = null;
    public ChildThread(String name)
    {
        this.name = name;
    }
    @Override
    public void run()
    {
        System.out.println(this.name + "--child thead begin");
        try
        {
            Thread.sleep(500);
        }
        catch (InterruptedException e)
        {
            System.out.println(e);
        }
        System.out.println(this.name + "--child thead over");
    }
}
 
 執(zhí)行結(jié)果如下:
parent thread begin
parent thread over
thread1--child thead begin
thread2--child thead begin

在這種情況下仍律,的確主線程退出,子線程就立刻結(jié)束了实柠,但是這是屬于JVM的底層實(shí)現(xiàn)機(jī)制水泉,并不是說主線程和子線程之間存在依賴關(guān)系。

linux中用top窒盐、ps命令查看進(jìn)程中的線程

在ps命令中茶行,“-T”選項(xiàng)可以開啟線程查看。下面的命令列出了由進(jìn)程號(hào)為<pid>的進(jìn)程創(chuàng)建的所有線程登钥。

1.$ ps -T -p <pid>

1.$ ps -T -p <pid>

top命令可以實(shí)時(shí)顯示各個(gè)線程情況畔师。要在top輸出中開啟線程查看,請(qǐng)調(diào)用top命令的“-H”選項(xiàng)牧牢,該選項(xiàng)會(huì)列出所有Linux線程看锉。在top運(yùn)行時(shí),你也可以通過按“H”鍵將線程查看模式切換為開或關(guān)塔鳍。
1.$ top -H

要讓top輸出某個(gè)特定進(jìn)程<pid>并檢查該進(jìn)程內(nèi)運(yùn)行的線程狀況:

2.$ top -H -p <pid>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伯铣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子轮纫,更是在濱河造成了極大的恐慌腔寡,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掌唾,死亡現(xiàn)場(chǎng)離奇詭異放前,居然都是意外死亡忿磅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門凭语,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葱她,“玉大人,你說我怎么就攤上這事似扔《中” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵炒辉,是天一觀的道長(zhǎng)豪墅。 經(jīng)常有香客問我,道長(zhǎng)黔寇,這世上最難降的妖魔是什么但校? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮啡氢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘术裸。我一直安慰自己倘是,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布袭艺。 她就那樣靜靜地躺著搀崭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猾编。 梳的紋絲不亂的頭發(fā)上瘤睹,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音答倡,去河邊找鬼轰传。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瘪撇,可吹牛的內(nèi)容都是我干的获茬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼倔既,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼恕曲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起渤涌,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤佩谣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后实蓬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茸俭,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吊履,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瓣履。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片率翅。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖袖迎,靈堂內(nèi)的尸體忽然破棺而出冕臭,到底是詐尸還是另有隱情,我是刑警寧澤燕锥,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布辜贵,位于F島的核電站,受9級(jí)特大地震影響归形,放射性物質(zhì)發(fā)生泄漏托慨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一暇榴、第九天 我趴在偏房一處隱蔽的房頂上張望厚棵。 院中可真熱鬧,春花似錦蔼紧、人聲如沸婆硬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彬犯。三九已至,卻和暖如春查吊,著一層夾襖步出監(jiān)牢的瞬間谐区,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工逻卖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宋列,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓评也,卻偏偏與公主長(zhǎng)得像虚茶,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仇参,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容