一砌们、線程簡介
1杆麸、什么是線程
????現(xiàn)代操作系統(tǒng)在運行一個程序時,會為其創(chuàng)建一個進程浪感。例如啟動一個java程序昔头,就會創(chuàng)建一個java進程。線程是操作系統(tǒng)最小的調(diào)度單位影兽,也叫做輕量級進程减细,是進程中實際的運作單位。在一個進程里可以創(chuàng)建多個線程赢笨,每個線程的實體都包含程序計數(shù)器未蝌、堆棧、保留局部變量茧妒、寄存器萧吠、少數(shù)狀態(tài)參數(shù)等,這些線程都能訪問共享的內(nèi)存變量桐筏。
線程和進程的區(qū)別是:
????進程是操作系統(tǒng)資源分配的基本單位纸型,線程是任務(wù)調(diào)度和執(zhí)行的基本單位,進程不是一個可執(zhí)行實體,線程是能獨立運行的基本單位狰腌。
????進程間相互獨立除破,進程有單獨完整的虛擬地址空間,而線程間共享所屬進程的資源對其他進程不可見琼腔。
????進程間通信主要是管道瑰枫、消息隊列、信號丹莲、共享存儲光坝、套接字,而線程可以直接讀寫進程數(shù)據(jù)段(如全局變量)來通信甥材。
????線程的上下文切換比進程的上下文切換要快的多盯另,因為線程共享所屬進程的虛擬空間內(nèi)存,所以線程的上下文切換不必切換虛擬空間內(nèi)存洲赵,代價要小的多鸳惯。
????一個java程序從main()方法開始執(zhí)行,java實際上天生就是多線程程序叠萍,執(zhí)行main方法的是名叫main的線程芝发,大量的其他子線程都是從它這里產(chǎn)生的,mian線程不是守護線程俭令,通常它是最后一個執(zhí)行完畢的線程。下面用JMX來查看一個普通的Java程序包含哪些線程部宿。
《java并發(fā)編程的藝術(shù)》第4章第1節(jié)例子:
public static void main(String[] args) {
????ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
????ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
????for (ThreadInfo threadInfo : threadInfos) {
????????System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
????}
}
書中給出的輸出內(nèi)容是:
????[4] Signal Dispatcher //分發(fā)處理發(fā)送給JVM信號的線程
????[3] Finalizer //調(diào)用對象 finalize 方法的線程
????[2] Reference Handler //清除Refernce的線程
????[1] main //main線程抄腔,用戶程序入口
據(jù)考證,在linux系統(tǒng)環(huán)境下會輸出書中一樣的結(jié)果
在windows下 jdk11會輸出:
????[1]main
????[2]Reference Handler
????[3]Finalizer
????[4]Signal Dispatcher
? ??[5]Attach Listener // jvm接收外部命令的線程理张,如果JVM啟動時每初始化赫蛇,那第一次執(zhí)行jvm命令時會啟動。
? ??[20]Common-Cleaner
在Mac系統(tǒng)下:
????[9] Monitor Ctrl-Break
????[4] Singal Dispatcher
????[3] Finalizer
????[2] Refernce Handler
????[1] main
2雾叭、為什么要使用多線程
????“執(zhí)行一個簡單的hello world程序悟耘,卻啟動了那么多無關(guān)線程,是不是把簡單的問題復(fù)雜化织狐?”暂幼,《java并發(fā)編程的藝術(shù)》書中回答說:當(dāng)然不是,因為正確使用多線程移迫,總是能夠給開發(fā)人員帶來顯著的好處....
????我認為如果只是執(zhí)行helloworld這種程序旺嬉,肯定不需要多線程環(huán)境執(zhí)行,但JVM的存在的意義不是執(zhí)行這么簡單的程序厨埋,而是根據(jù)需求得出最佳的編程模型邪媳,以較高的效率執(zhí)行代碼。所以,使用多線程的原因主要有以下幾點:
????1雨效、從執(zhí)行效率上講迅涮,在多核CPU系統(tǒng)上,將要執(zhí)行的任務(wù)分割成多個任務(wù)并行執(zhí)行徽龟,就可以顯著提高執(zhí)行效率叮姑。單核單線程處理器上,只能實現(xiàn)并發(fā)顿肺,也就是在一個時間區(qū)間內(nèi)戏溺,任務(wù)根據(jù)系統(tǒng)分配的時間片上下文切換先后執(zhí)行,因為時間片非常短屠尊,所以感覺是同時執(zhí)行的旷祸,但實際上不考慮阻塞會比單線程更慢。而并行執(zhí)行可以充分利用多核CPU的優(yōu)勢讼昆,多個任務(wù)同時跑在CPU上托享,無疑顯著的減少程序處理時間,提高代碼的執(zhí)行效率浸赫。
????2闰围、從程序設(shè)計角度上講,在一些適合的場合既峡,用多線程可以避免阻塞羡榴。比如在網(wǎng)絡(luò)編程上,IO操作會導(dǎo)致當(dāng)前線程阻塞运敢,但如果采用多線程校仑,可以避免因IO操作而導(dǎo)致無法接收其他客戶端的連接請求。在實際開發(fā)中传惠,比如查詢數(shù)據(jù)庫的操作迄沫,會導(dǎo)致當(dāng)前線程阻塞直到得到查詢結(jié)果,這對服務(wù)器程序來講無法接受卦方,所以可以在主線程外另起一個線程來做查詢操作羊瘩,將一些后續(xù)的操作放在這個線程里處理,可以充分避免因查詢阻塞而導(dǎo)致服務(wù)器卡頓的情況盼砍。????
二尘吗、線程的使用
1、線程優(yōu)先級
? ??線程優(yōu)先級在早期的java版本中可能很有用浇坐,但現(xiàn)在不推薦使用它摇予。在java線程中,可以通過一個整型成員變量priority來控制優(yōu)先級吗跋,范圍從1到10侧戴,在線程構(gòu)建時可通過setPriority來修改優(yōu)先級宁昭,默認優(yōu)先級是5。優(yōu)先級高的獲得時間片的幾率會大于優(yōu)先級低的酗宋。
????線程優(yōu)先級不能作為程序正確性的依賴积仗,因為操作系統(tǒng)可能完全不理會java線程對優(yōu)先級的設(shè)定。
2蜕猫、線程的狀態(tài)
????java線程在運行的生命周期中可能處于6種不同的狀態(tài)寂曹,在一個給定的時刻,線程只能處于其中的一個狀態(tài)回右。
????狀態(tài)名稱? ? ????????????說明
????NEW? ? ? ? ? ????????????初始狀態(tài)隆圆,線程被構(gòu)建,但還沒調(diào)用start方法
????RUNNABLE? ? ? ? ? ?運行狀態(tài)翔烁,Java線程將操作系統(tǒng)中的就緒和運行兩種狀態(tài)籠統(tǒng)地稱作“運行中”
????BLOCKED? ? ? ? ? ? ?阻塞狀態(tài)渺氧,表示線程阻塞于鎖
????WAITING? ? ? ? ? ? ? ?等待狀態(tài),表示線程進入等待狀態(tài)蹬屹,進入該狀態(tài)表示當(dāng)前線程需要等待其他線程做出一些特定動作(通知或中斷)
????TIME_WAITING? ? 超時等待狀態(tài)侣背,該狀態(tài)不同于WAITING,它是可以在指定的時間自行返回的
????TERMINATED? ? ? ?終止狀態(tài)慨默,表示當(dāng)前線程已經(jīng)執(zhí)行完畢
3贩耐、Daemon守護線程
????Daemon線程是一種支持型線程,因為它主要被用作程序中后臺調(diào)度以及支持性工作厦取。這意味著潮太,當(dāng)一個java虛擬機中不存在非Daemon線程的時候骨杂,Java虛擬機將會退出涩馆。可以調(diào)用Thread.setDaemon(true)將線程設(shè)置為Daemon線程低淡,但要在線程執(zhí)行start方法之前初始狀態(tài)設(shè)置台谢,不能在啟動線程之后設(shè)置寻狂。
????Daemon線程被用作完成支持性工作岁经,但是在java虛擬機退出時Daemon線程中的finally塊并不一定會執(zhí)行朋沮,所以在構(gòu)建Daemon線程時,不能依靠finally塊中的內(nèi)容來確保執(zhí)行關(guān)閉或清理資源的邏輯缀壤。
4樊拓、線程的啟動和終止
4.1 構(gòu)造線程:
????在運行線程之前首先要構(gòu)造一個線程對象,線程對象在構(gòu)造的時候需要提供線程所需要的屬性塘慕,如線程所屬的線程組筋夏、線程優(yōu)先級、是否是Daemon線程等信息图呢。
????在構(gòu)建過程中条篷,一個新構(gòu)造的線程對象是由其parent線程來進行空間分配的骗随,而child線程繼承了parent是否為Daemon、優(yōu)先級和加載資源的contextClassLoader以及可繼承的ThreadLocal赴叹,同時還會分配一個唯一ID來標識這個child線程鸿染。至此,一個能夠運行得線程對象就初始化好了乞巧,在堆內(nèi)存中等待著運行涨椒。
? ??創(chuàng)建線程的四種方式:繼承Thread類、實現(xiàn)runnable接口绽媒、實現(xiàn)callable接口蚕冬、使用線程池創(chuàng)建
4.2 啟動線程:
????線程對象初始化完成后,調(diào)用start()方法就可以啟動這個線程是辕。線程start()方法的含義是:當(dāng)前線程(即parent線程)同步告知Java虛擬機囤热,只要線程規(guī)劃器空閑,應(yīng)立即啟動調(diào)用start()方法的線程免糕,調(diào)用該線程的run()方法赢乓。
4.3 理解中斷:
????在程序中免不得要碰到需要暫停或停止當(dāng)前線程的情況石窑,比如負責(zé)下載的程序會占用大部分帶寬牌芋,用戶想玩網(wǎng)游就要暫停下載任務(wù),這個時候就要停止當(dāng)前的線程讓出帶寬給用戶玩游戲松逊,而這種情況就需要用到線程的中斷機制躺屁。
????中斷可以理解為線程的一個標識位屬性,它表示一個運行中的線程是否被其他線程進行了中斷操作经宏。中斷好比其他線程對該線程打了個招呼犀暑,其他線程調(diào)用該線程的interrupt()方法對其進行中斷操作。
????線程通過方法isInterrupted()來進行判斷是否被中斷烁兰,也可以調(diào)用靜態(tài)方法Thread.interrupted()對當(dāng)前線程的中斷標識位進行復(fù)位耐亏。如果該線程已經(jīng)處于終結(jié)狀態(tài),即使該線程被中斷過沪斟,在調(diào)用該線程對象的isInterrupted()時依舊會返回false广辰。
????從Java的API中可以看到,許多聲明拋出InterruptedException的方法(例如Thread.sleep(long millis)方法)這些方法在拋出InterruptedException之前主之,Java虛擬機會先將該線程的中斷標識位清除择吊,然后拋出InterruptedException,此時調(diào)用isInterrupted()方法將會返回false槽奕。
????可以看一下以下例子觀察兩個線程的中斷標識位几睛。
????輸出的結(jié)果:
????從結(jié)果可以看出,拋出InterruptedException的線程SleepThread粤攒,其中斷標識位被清除了所森,而一直忙碌運作的線程BusyThread囱持,中斷標識位沒有被清除。
4.4 過期的suspend()焕济、resume()和stop()
????大家對于CD機肯定不會陌生洪唐,如果把它播放音樂比作一個線程的運作,那么對音樂播放做出的暫停吼蚁、恢復(fù)和停止操作對應(yīng)在線程Thread的API就是suspend()凭需、resume()和stop()。
????在以下代碼中肝匆,創(chuàng)建了一個線程PrintThread粒蜈,它以1秒的頻率進行打印,而主線程對其進行暫停旗国、恢復(fù)和停止操作枯怖。
????輸出如下:(輸出內(nèi)容中的時間與示例執(zhí)行的具體時間相關(guān))
????在執(zhí)行過程中,PrintThread運行了3秒能曾,隨后被暫停度硝,3秒后恢復(fù),最后經(jīng)過3秒被終止寿冕。通過示例的輸出可以看到蕊程,suspend()、resume()和stop()方法完成了線程的暫停驼唱、恢復(fù)和終止藻茂,而且非常“人性化”玫恳。但是這些API是過期的辨赐,不建議使用。
????不建議使用的主要原因有:suspend()方法在調(diào)用后京办,線程不會釋放已經(jīng)占有的資源(比如鎖)掀序,而是占有著資源進入睡眠狀態(tài),這樣容易引發(fā)死鎖問題惭婿。同樣不恭,stop()方法在終結(jié)一個線程時不會保證線程的資源正常釋放,通常是沒有給線程完成資源釋放工作的機會审孽,因此會導(dǎo)致程序可能工作在不確定狀態(tài)下县袱。
????暫停和恢復(fù)操作可以用等待/通知機制替代浑娜。
4.5 安全地終止線程
????可以采用對線程設(shè)置中斷標識后安全的終止線程佑力。使用interrupt()方法設(shè)置線程的中斷標識位,由該線程檢測進行中斷筋遭。另外還可以在該線程中自主增加一個boolean類型用于判斷是否終止該線程打颤。
????輸出結(jié)果:(輸出內(nèi)容可能不同)
三暴拄、常見面試題問題
????1、線程和進程的區(qū)別是什么
????2编饺、線程的狀態(tài)有哪幾種
????3乖篷、Java中創(chuàng)建線程有幾種方式,你喜歡哪一種透且,為什么
????4撕蔼、如何安全的終止線程
????5、為什么不推薦使用suspend()秽誊、resume()和stop()
????摘抄出處:《java并發(fā)編程的藝術(shù)》