傳統(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)度。
這樣做有一些優(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)中的線程**
特別注意:這些線程的狀態(tài)時(shí)JVM中的線程狀態(tài)蛛淋!不是操作系統(tǒng)中的線程狀態(tài)。
操作系統(tǒng)中的進(jìn)程(線程)狀態(tài)**(區(qū)分和JVM中的線程狀態(tài))
這里需要著重解釋一點(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)換
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()方法
- 限期等待(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>