一、線程與進(jìn)程
線程定義
進(jìn)程中執(zhí)行的一個(gè)代碼段翠语,來完成不同的任務(wù)
組成:線程ID叽躯,當(dāng)前指令指針(PC),寄存器集合(存儲(chǔ)一部分正在執(zhí)行線程的處理器狀態(tài)的值)和堆棧
進(jìn)程定義
執(zhí)行的一段程序肌括,一旦程序被載入到內(nèi)存中準(zhǔn)備執(zhí)行就是一個(gè)進(jìn)程
組成:文本區(qū)域(存儲(chǔ)處理器執(zhí)行的代碼)点骑、數(shù)據(jù)區(qū)域(存儲(chǔ)變量和進(jìn)程執(zhí)行期間的動(dòng)態(tài)分配的內(nèi)存)和堆棧(存儲(chǔ)著活動(dòng)過程調(diào)用的指令和本地變量)
例如Windows系統(tǒng)中運(yùn)行的一個(gè)exe就是一個(gè)進(jìn)程
進(jìn)入任務(wù)管理器,可以查看系統(tǒng)運(yùn)行的進(jìn)程谍夭,以及每個(gè)進(jìn)程中的線程數(shù)
線程與進(jìn)程的關(guān)系
1.? 進(jìn)程是系統(tǒng)內(nèi)存分配的最小單位黑滴,線程是系統(tǒng)調(diào)度的最小單位
進(jìn)程擁有自己的內(nèi)存空間,因?yàn)榫€程是屬于進(jìn)程的慧库,多線程直接共享該進(jìn)程中內(nèi)存跷跪,提高了程序的運(yùn)行效率
2.? 一個(gè)程序至少有一個(gè)進(jìn)程馋嗜,一個(gè)進(jìn)程中包括一條 or 多條線程齐板,線程不能獨(dú)立于進(jìn)程
在Java中,每次程序運(yùn)行至少啟動(dòng)2個(gè)線程:一個(gè)是main線程葛菇,一個(gè)是垃圾收集線程甘磨。因?yàn)槊慨?dāng)使用 Java 命令執(zhí)行一個(gè)類時(shí),都會(huì)啟動(dòng)一個(gè)JVM眯停,每一個(gè) JVM 實(shí)際上就是在操作系統(tǒng)中啟動(dòng)了一個(gè)進(jìn)程
3.? 進(jìn)程與線程都可以并發(fā)執(zhí)行
問題:如何了解 “并發(fā)” 執(zhí)行 济舆,它與 “并行”執(zhí)行一樣嗎?
并行執(zhí)行:從宏觀和微觀的角度莺债,都是同時(shí)執(zhí)行的
并發(fā)執(zhí)行:從宏觀角度滋觉,似乎是同時(shí)執(zhí)行;但從微觀角度齐邦,不是同時(shí)執(zhí)行
操作系統(tǒng)采取時(shí)間片的機(jī)制椎侠,使多個(gè)進(jìn)程(線程)快速切換執(zhí)行,在宏觀上就有并行執(zhí)行的錯(cuò)覺
在單核情況下措拇,不存在并行執(zhí)行我纪;但在多核情況下,進(jìn)程(線程)分布在不同的CPU中丐吓,可以并行執(zhí)行程序
二浅悉、線程的生命周期
線程是一個(gè)動(dòng)態(tài)執(zhí)行的過程
1.? 新建狀態(tài) New
創(chuàng)建線程對(duì)象,進(jìn)入新建狀態(tài)券犁,此時(shí)線程屬于 not? alive术健,直到執(zhí)行 start()
創(chuàng)建線程: 使用 new 關(guān)鍵字和 Thread 類或其子類, 例如:Thread?? t? = new? MyThread();
2.? 就緒狀態(tài) Runnable
調(diào)用線程對(duì)象的 start() 方法粘衬,進(jìn)入就緒狀態(tài)荞估,此時(shí)線程屬于 alive 比被,但還未進(jìn)入執(zhí)行,只是做好了被 CPU 調(diào)度的準(zhǔn)備
3.? 運(yùn)行狀態(tài) Running
當(dāng)線程獲取到CPU泼舱,進(jìn)入運(yùn)行狀態(tài)等缀,線程的 run() 方法才開始被執(zhí)行,此時(shí)線程屬于 alive
只有當(dāng)線程處于就緒狀態(tài)娇昙,才能被CPU調(diào)度尺迂,所以就緒狀態(tài)是運(yùn)行狀態(tài)的唯一入口
4.? 阻塞狀態(tài) Blocked
處于運(yùn)行狀態(tài)的線程,由于某種原因冒掌,放棄使用CPU噪裕,停止運(yùn)行,進(jìn)入阻塞狀態(tài)股毫,此時(shí)線程屬于 alive
同步阻塞:
同步鎖 synchronized膳音,當(dāng)某線程占有了該同步鎖, 則其他線程就不能進(jìn)入到同步鎖中铃诬,則這些線程就會(huì)進(jìn)入阻塞狀態(tài)
當(dāng)在阻塞隊(duì)列的線程獲取到同步鎖時(shí)祭陷,才能進(jìn)入到就緒狀態(tài)等待被調(diào)度
等待阻塞:(理解得有點(diǎn)繞)
調(diào)用線程的 wait() ,線程進(jìn)入等待狀態(tài)趣席,此時(shí)會(huì)釋放占用的 CPU 資源和鎖(wait()方法需要在鎖中使用)
當(dāng)被其他線程調(diào)用 notify() 喚醒之后兵志,需要重新獲取對(duì)象的鎖,所以會(huì)先進(jìn)入Blocked狀態(tài)宣肚,才會(huì)進(jìn)入就緒狀態(tài)
其他阻塞:
調(diào)用線程的 sleep() 或 join() 或 發(fā)出了I/O請(qǐng)求想罕,線程會(huì)進(jìn)入到阻塞狀態(tài)
當(dāng) sleep() 狀態(tài)超時(shí)、join() 等待線程終止或者超時(shí)霉涨、或者 I/O 處理完畢時(shí)按价,線程重新進(jìn)入就緒狀態(tài)
5.? 死亡狀態(tài) Dead
當(dāng)一個(gè)線程的 run() 方法運(yùn)行完畢 or 被中斷 or 被異常退出,該線程進(jìn)入死亡狀態(tài)
三笙瑟、線程的創(chuàng)建
-?? 實(shí)現(xiàn) Runnable 接口楼镐,實(shí)例化 Thread 類(線程無返回值)
- ? 繼承 Thread 類,重寫 Thread 的 run() 方法(線程無返回值)
-?? 實(shí)現(xiàn) Callable 接口逮走,通過 FutureTask 包裝器創(chuàng)建線程(線程有返回值)
1.? 實(shí)現(xiàn) Runnable 接口鸠蚪,實(shí)例化 Thread 類(線程無返回值)
step1:? 創(chuàng)建一個(gè)類,例如 RunnableThread师溅,實(shí)現(xiàn) Runnable 接口
step2:? 實(shí)例化 RunnableThread 對(duì)象茅信, 創(chuàng)建 Thread 對(duì)象,將 RunnableThread 作為參數(shù)傳給 Thread 類的構(gòu)造函數(shù)墓臭,然后通過 Thread.start() 方法啟動(dòng)線程
運(yùn)行結(jié)果
問題: 為什么創(chuàng)建 RunnableThread 對(duì)象后蘸鲸,需要將它和 Thread 對(duì)象進(jìn)行關(guān)聯(lián)?
查看 Runnable 接口的源代碼窿锉,可以看到 Runnable 接口只有一個(gè) run() 方法酌摇,所以需要通過 Thread 類的 start() 方法來啟動(dòng)線程
啟動(dòng)線程后膝舅,Thread 類中的 run() 方法會(huì)先判斷傳入的 target Runnable 對(duì)象的 run() 方法是否為空,若不為空窑多,則調(diào)用 target Runnable 對(duì)象的 run() 方法
而且仍稀,RunnableThread 類實(shí)現(xiàn) Runnbale 接口中不能直接使用 Thread 類中的方法,需要先獲取到Thread 對(duì)象后埂息,才能調(diào)用 Thread 方法
2.? 繼承 Thread 類技潘,重寫 Thread 的 run() 方法(線程無返回值)
step1:? 創(chuàng)建一個(gè)類,例如 MyThread千康,繼承 Thread 類享幽,重寫 Thread 的 run() 方法
step2:? 實(shí)例化 MyThread 對(duì)象,直接調(diào)用 start() 方法啟動(dòng)線程
運(yùn)行結(jié)果
問題:實(shí)現(xiàn) Runnable 接口 和 繼承 Thread 類拾弃,運(yùn)行結(jié)果不一樣值桩,這是為什么?
繼承 Thread 類和實(shí)現(xiàn) Runnable 接口實(shí)現(xiàn)多線程豪椿,會(huì)發(fā)現(xiàn)這是兩個(gè)不同的實(shí)現(xiàn)多線程
繼承 Thread 類是多個(gè)線程分別完成自己的任務(wù)
實(shí)現(xiàn) Runnable 接口是多個(gè)線程共同完成一個(gè)任務(wù)奔坟,其實(shí)用繼承Thread類也可以實(shí)現(xiàn),只是比較麻煩
這樣的話砂碉,實(shí)現(xiàn) Runnable 接口比繼承 Thread 類具有一定的優(yōu)勢
1)適合多個(gè)相同的程序代碼的線程去處理同一個(gè)資源
2)可以避免 Java 中的單繼承的限制
當(dāng)一個(gè)類繼承 Thread 類后蛀蜜,則不能在繼承別的類,而接口比較靈活增蹭,可以實(shí)現(xiàn)多個(gè)接口,而且實(shí)現(xiàn)接口了還可繼續(xù)繼承一個(gè)類
3)增加程序的健壯性磅摹,代碼可以被多個(gè)線程共享滋迈,代碼和數(shù)據(jù)獨(dú)立
參考鏈接:https://www.cnblogs.com/CryOnMyShoulder/p/8028122.html
?????????????????https://www.cnblogs.com/xubiao/p/5418141.html
3.? 實(shí)現(xiàn) Callable 接口,通過 FutureTask 包裝器創(chuàng)建線程(線程有返回值)
step1:? 創(chuàng)建一個(gè)類户誓,例如 CallableThread饼灿,實(shí)現(xiàn) Callable 接口,重寫 Callable 接口的 call() 方法
step2:? 實(shí)例化 CallableThread 對(duì)象帝美,使用 FutureTask 類來包裝 CallableThread 對(duì)象
然后 FutureTask 對(duì)象作為參數(shù)傳給 Thread 類的構(gòu)造函數(shù)碍彭,通過 Thread.start() 方法啟動(dòng)線程
使用 FutureTask.get() 得到 Callable 接口的 call() 方法的返回值
返回結(jié)果
Callable 和 Runnable 相似,類實(shí)例都需要被 Thread 執(zhí)行悼潭,但 Callable 接口能返回一個(gè)值或者拋出一個(gè)異常庇忌,Runnable 不能
實(shí)現(xiàn) Callable 接口需要重寫其唯一的 call() 方法
FutureTask 實(shí)現(xiàn)了 Runable 接口 和 Future 接口,所以如果想 Callable 實(shí)例作為 Thread 的執(zhí)行體就必須通過 FutureTask 來作為橋梁