12.4 Java與線程
12.4.1 線程的實(shí)現(xiàn)
實(shí)現(xiàn)線程主要有三種方式:使用內(nèi)核線程實(shí)現(xiàn)、使用用戶線程實(shí)現(xiàn)、使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)厉斟。
1、使用內(nèi)核線程實(shí)現(xiàn)
內(nèi)核線程就是直接由操作系統(tǒng)內(nèi)核支持的線程强衡。
程序一般不會(huì)直接去使用內(nèi)核線程擦秽,而是去使用內(nèi)核線程的一種高級(jí)接口—輕量級(jí)進(jìn)程,輕量級(jí)進(jìn)程就是我們通常意義上講的線程漩勤,由于每個(gè)輕量級(jí)進(jìn)程都由一個(gè)內(nèi)核線程支持感挥,因些只有先支持內(nèi)核線程,才能有輕量級(jí)進(jìn)程越败。這種輕量級(jí)進(jìn)程與內(nèi)核線程之間的1:1的關(guān)系稱為一對(duì)一的線程模型触幼,如下圖所示。
由于每個(gè)輕量級(jí)進(jìn)程都需要有一個(gè)內(nèi)核線程的支持究飞,因此輕量級(jí)進(jìn)程要消耗一定的內(nèi)核資源(如內(nèi)核線程的椫们空間),因此一個(gè)系統(tǒng)支持輕量級(jí)進(jìn)程的數(shù)量是有限的亿傅。
2霉祸、使用用戶線程實(shí)現(xiàn)
廣義上來講,一個(gè)線程只要不是內(nèi)核線程袱蜡,那就可以認(rèn)為是用戶線程,因此從這個(gè)定義上來講輕量級(jí)進(jìn)程也屬于用戶線程慢宗。
而狹義上的用戶線程指的是完全建立在用戶空間的線程庫上坪蚁,系統(tǒng)內(nèi)核不能感知到線程存在的實(shí)現(xiàn)奔穿。用戶線程的建立、同步敏晤、銷毀和調(diào)度完全在用戶態(tài)中完成贱田,不需要內(nèi)核的幫助。如果程序?qū)崿F(xiàn)得當(dāng)嘴脾,這種線程不需要切換到內(nèi)核態(tài)男摧,因操作可以是非常快速且低消耗的译打,也可以支持規(guī)模更大的線程數(shù)量耗拓,部分高性能的數(shù)據(jù)庫中的多線程就是由用戶線程實(shí)現(xiàn)的。這進(jìn)程與用戶線程之間1:N的關(guān)系稱為一對(duì)多的線程模型奏司,如下圖所示乔询。
使用用戶線程的優(yōu)勢(shì)在于不需要系統(tǒng)內(nèi)核支持,劣勢(shì)也在于沒有系統(tǒng)內(nèi)核的支持韵洋,所有的線程操作都需要用戶程序自己處理竿刁。因而使用用戶線程實(shí)現(xiàn)的程序一般都比較復(fù)雜。
3搪缨、混合實(shí)現(xiàn)
將內(nèi)核線程與用戶線程一起使用的實(shí)現(xiàn)方式食拜。集兩者的優(yōu)點(diǎn)于一身。
4副编、Java線程的實(shí)現(xiàn)
Java線程在JDK1.2之前负甸,是用戶線程實(shí)現(xiàn)的,而在JDK1.2中齿桃,線程模型被替換為基于操作系統(tǒng)原生線程模型來實(shí)現(xiàn)惑惶。
12.4.2 Java線程調(diào)度
線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過程,主要調(diào)度方式有兩種短纵,分別是協(xié)同式線程調(diào)度和搶占式線程調(diào)度带污。
使用協(xié)同式線程調(diào)度的多線程系統(tǒng),線程執(zhí)行的時(shí)間由線程本身控制香到,線程把自己的工作執(zhí)行完成后鱼冀,主動(dòng)通知系統(tǒng)切換到另外一個(gè)線程上去。優(yōu)點(diǎn)的實(shí)現(xiàn)簡(jiǎn)單悠就,切換操作對(duì)線程自己是可知的千绪,所以沒有同步問題。缺點(diǎn)是如果有一個(gè)線程有問題一直不告知系統(tǒng)進(jìn)行線程切換梗脾,那么程序就會(huì)一直阻塞在那里荸型。
使用搶占式調(diào)度的多線程系統(tǒng),那么每個(gè)線程將由系統(tǒng)來分配執(zhí)行時(shí)間炸茧,在這種實(shí)現(xiàn)線程調(diào)度的方式下瑞妇,線程執(zhí)行時(shí)間是系統(tǒng)可控的稿静,也不會(huì)有一個(gè)線程導(dǎo)致整個(gè)進(jìn)程阻塞的問題,Java使用是的搶占式來實(shí)現(xiàn)多線程的辕狰。
Java中可以通過設(shè)置線程優(yōu)先級(jí)調(diào)節(jié)線程的招行時(shí)間改备。不過線程優(yōu)先級(jí)并不是太靠譜,原因是Java的線程是被映射到系統(tǒng)的原生線程上來實(shí)現(xiàn)的蔓倍,交由操作系統(tǒng)來調(diào)度悬钳,雖然現(xiàn)在很多操作系統(tǒng)都提供線程優(yōu)先級(jí)的概念,但是并不見得能與Java線程的優(yōu)先級(jí)一一對(duì)應(yīng)偶翅。
12.4.3 狀態(tài)轉(zhuǎn)換
1默勾、新建狀態(tài)(New):新創(chuàng)建了一個(gè)線程對(duì)象。
2倒堕、就緒狀態(tài)(Runnable):線程對(duì)象創(chuàng)建后灾测,其他線程調(diào)用了該對(duì)象的start()方法。該狀態(tài)的線程位于“可運(yùn)行線程池”中垦巴,變得可運(yùn)行媳搪,只等待獲取CPU的使用權(quán)。即在就緒狀態(tài)的進(jìn)程除CPU之外骤宣,其它的運(yùn)行所需資源都已全部獲得秦爆。
3、運(yùn)行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU憔披,執(zhí)行程序代碼等限。
4、阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán)芬膝,暫時(shí)停止運(yùn)行望门。直到線程進(jìn)入就緒狀態(tài),才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)锰霜。
阻塞的情況分三種:
(1)筹误、等待阻塞:運(yùn)行的線程執(zhí)行wait()方法,該線程會(huì)釋放占用的所有資源癣缅,JVM會(huì)把該線程放入“等待池”中厨剪。進(jìn)入這個(gè)狀態(tài)后,是不能自動(dòng)喚醒的友存,必須依靠其他線程調(diào)用notify()或notifyAll()方法才能被喚醒祷膳。
(2)、同步阻塞:運(yùn)行的線程在獲取對(duì)象的同步鎖時(shí)屡立,若該同步鎖被別的線程占用直晨,則JVM會(huì)把該線程放入“鎖池”中。
(3)、其他阻塞:運(yùn)行的線程執(zhí)行sleep()或join()方法抡秆,或者發(fā)出了I/O請(qǐng)求時(shí)奕巍,JVM會(huì)把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)儒士、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí)檩坚,線程重新轉(zhuǎn)入就緒狀態(tài)着撩。
5、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法匾委,該線程結(jié)束生命周期拖叙。
注:拿到對(duì)象的鎖標(biāo)記,即為獲得了對(duì)該對(duì)象(臨界區(qū))的使用權(quán)限赂乐。即該線程獲得了運(yùn)行所需的資源薯鳍,進(jìn)入“就緒狀態(tài)”,只需獲得CPU挨措,就可以運(yùn)行挖滤。因?yàn)楫?dāng)調(diào)用wait()后,線程會(huì)釋放掉它所占有的“鎖標(biāo)志”浅役,所以線程只有在此獲取資源才能進(jìn)入就緒狀態(tài)斩松。