一. JAVA 并發(fā)知識庫
二. JAVA 線程實(shí)現(xiàn)/創(chuàng)建方式
1. 繼承 Thread 類
Thread 類本質(zhì)上是實(shí)現(xiàn)了 Runnable 接口的一個(gè)實(shí)例姓建,代表一個(gè)線程的實(shí)例。啟動線程的唯一方 法就是通過 Thread 類的 start()實(shí)例方法缤苫。start()方法是一個(gè) native 方法速兔,它將啟動一個(gè)新線 程,并執(zhí)行 run()方法活玲。
2. 實(shí)現(xiàn) Runnable 接口涣狗。
如果自己的類已經(jīng) extends 另一個(gè)類,就無法直接 extends Thread舒憾,此時(shí)镀钓,可以實(shí)現(xiàn)一個(gè)
Runnable 接口。
3. ExecutorService镀迂、Callable<Class>掸宛、Future 有返回值線程
有返回值的任務(wù)必須實(shí)現(xiàn) Callable 接口,類似的招拙,無返回值的任務(wù)必須 Runnable 接口唧瘾。執(zhí)行 Callable 任務(wù)后措译,可以獲取一個(gè) Future 的對象,在該對象上調(diào)用 get 就可以獲取到 Callable 任務(wù) 返回的 Object 了饰序,再結(jié)合線程池接口 ExecutorService 就可以實(shí)現(xiàn)傳說中有返回結(jié)果的多線程 了领虹。
4. 基于線程池的方式
線程和數(shù)據(jù)庫連接這些資源都是非常寶貴的資源。那么每次需要的時(shí)候創(chuàng)建求豫,不需要的時(shí)候銷 毀塌衰,是非常浪費(fèi)資源的。那么我們就可以使用緩存的策略蝠嘉,也就是使用線程池最疆。
三. 種線程池
Java 里面線程池的頂級接口是?Executor,但是嚴(yán)格意義上講 Executor 并不是一個(gè)線程池蚤告,而 只是一個(gè)執(zhí)行線程的工具努酸。真正的線程池接口是?ExecutorService。
1. newCachedThreadPool
創(chuàng)建一個(gè)可根據(jù)需要創(chuàng)建新線程的線程池杜恰,但是在以前構(gòu)造的線程可用時(shí)將重用它們获诈。對于執(zhí)行 很多短期異步任務(wù)的程序而言,這些線程池通承暮郑可提高程序性能舔涎。調(diào)用 execute 將重用以前構(gòu)造的線程(如果線程可用)。如果現(xiàn)有線程沒有可用的逗爹,則創(chuàng)建一個(gè)新線程并添加到池中亡嫌。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。因此掘而,長時(shí)間保持空閑的線程池不會使用任何資源昼伴。
2. newFixedThreadPool
創(chuàng)建一個(gè)可重用固定線程數(shù)的線程池,以共享的無界隊(duì)列方式來運(yùn)行這些線程镣屹。在任意點(diǎn)圃郊,在大多數(shù) nThreads 線程會處于處理任務(wù)的活動狀態(tài)。如果在所有線程處于活動狀態(tài)時(shí)提交附加任務(wù)女蜈,則在有可用線程之前,附加任務(wù)將在隊(duì)列中等待逸寓。如果在關(guān)閉前的執(zhí)行期間由于失敗而導(dǎo)致任何線程終止覆山,那么一個(gè)新線程將代替它執(zhí)行后續(xù)的任務(wù)(如果需要)竹伸。在某個(gè)線程被顯式地關(guān)閉之前,池中的線程將一直存在。
3. newScheduledThreadPool
創(chuàng)建一個(gè)線程池勋篓,它可安排在給定延遲后運(yùn)行命令或者定期地執(zhí)行吧享。
4. newSingleThreadExecutor
Executors.newSingleThreadExecutor()返回一個(gè)線程池(這個(gè)線程池只有一個(gè)線程),這個(gè)線程 池可以在線程死后(或發(fā)生異常時(shí))重新啟動一個(gè)線程來替代原來的線程繼續(xù)執(zhí)行下去!
四. 線程生命周期(狀態(tài))
當(dāng)線程被創(chuàng)建并啟動以后譬嚣,它既不是一啟動就進(jìn)入了執(zhí)行狀態(tài)钢颂,也不是一直處于執(zhí)行狀態(tài)。 在線程的生命周期中拜银,它要經(jīng)過新建(New)、就緒(Runnable)尼桶、運(yùn)行(Running)、阻塞 (Blocked)和死亡(Dead)5 種狀態(tài)趾盐。尤其是當(dāng)線程啟動以后,它不可能一直"霸占"著 CPU 獨(dú)自 運(yùn)行谤碳,所以 CPU 需要在多條線程之間切換溢豆,于是線程狀態(tài)也會多次在運(yùn)行、阻塞之間切換
1. 新建狀態(tài)(NEW)
當(dāng)程序使用 new 關(guān)鍵字創(chuàng)建了一個(gè)線程之后瘸羡,該線程就處于新建狀態(tài)漩仙,此時(shí)僅由 JVM 為其分配內(nèi)存队他,并初始化其成員變量的值
2. 就緒狀態(tài)(RUNNABLE):
當(dāng)線程對象調(diào)用了 start()方法之后粘昨,該線程處于就緒狀態(tài)。Java 虛擬機(jī)會為其創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器馁启,等待調(diào)度運(yùn)行。
3. 運(yùn)行狀態(tài)(RUNNING):
如果處于就緒狀態(tài)的線程獲得了 CPU,開始執(zhí)行 run()方法的線程執(zhí)行體,則該線程處于運(yùn)行狀態(tài)。
4. 阻塞狀態(tài)(BLOCKED):
阻塞狀態(tài)是指線程因?yàn)槟撤N原因放棄了 cpu 使用權(quán),也即讓出了 cpu timeslice歹袁,暫時(shí)停止運(yùn)行。直到線程進(jìn)入可運(yùn)行(runnable)狀態(tài)寝优,才有機(jī)會再次獲得 cpu timeslice 轉(zhuǎn)到運(yùn)行(running)狀態(tài)乏矾。阻塞的情況分三種:
等待阻塞(o.wait->等待對列):
運(yùn)行(running)的線程執(zhí)行 o.wait()方法,JVM 會把該線程放入等待隊(duì)列(waitting queue) 中钻心。
同步阻塞(lock->鎖池)
運(yùn)行(running)的線程在獲取對象的同步鎖時(shí)凄硼,若該同步鎖被別的線程占用捷沸,則 JVM 會把該線 程放入鎖池(lock pool)中。
其他阻塞(sleep/join)
運(yùn)行(running)的線程執(zhí)行 Thread.sleep(long ms)或 t.join()方法痒给,或者發(fā)出了 I/O 請求時(shí)说墨, JVM 會把該線程置為阻塞狀態(tài)婉刀。當(dāng) sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)序仙、或者 I/O 處理完畢時(shí)突颊,線程重新轉(zhuǎn)入可運(yùn)行(runnable)狀態(tài)。
5. 線程死亡(DEAD)
線程會以下面三種方式結(jié)束,結(jié)束后就是死亡狀態(tài)律秃。
正常結(jié)束
1. run()或 call()方法執(zhí)行完成爬橡,線程正常結(jié)束。
異常結(jié)束
2. 線程拋出一個(gè)未捕獲的 Exception 或 Error棒动。
調(diào)用 stop
3. 直接調(diào)用該線程的 stop()方法來結(jié)束該線程—該方法通常容易導(dǎo)致死鎖糙申,不推薦使用。
五. 終止線程 4 種方式
1. 正常運(yùn)行結(jié)束
程序運(yùn)行結(jié)束船惨,線程自動結(jié)束柜裸。
2. 使用退出標(biāo)志退出線程
一般 run()方法執(zhí)行完,線程就會正常結(jié)束粱锐,然而疙挺,常常有些線程是伺服線程。它們需要長時(shí)間的運(yùn)行怜浅,只有在外部某些條件滿足的情況下铐然,才能關(guān)閉這些線程。使用一個(gè)變量來控制循環(huán)恶座,例如:最直接的方法就是設(shè)一個(gè) boolean 類型的標(biāo)志搀暑,并通過設(shè)置這個(gè)標(biāo)志為 true 或 false 來控制 while循環(huán)是否退出,代碼示例:
定義了一個(gè)退出標(biāo)志 exit跨琳,當(dāng) exit 為 true 時(shí)自点,while 循環(huán)退出,exit 的默認(rèn)值為 false.在定義 exit時(shí)湾宙,使用了一個(gè) Java 關(guān)鍵字 volatile樟氢,這個(gè)關(guān)鍵字的目的是使 exit 同步冈绊,也就是說在同一時(shí)刻只能由一個(gè)線程來修改 exit 的值侠鳄。
3. Interrupt 方法結(jié)束線程
使用 interrupt()方法來中斷線程有兩種情況:
1. 線程處于阻塞狀態(tài):如使用了 sleep,同步鎖的 wait,socket 中的 receiver,accept 等方法時(shí),會使線程處于阻塞狀態(tài)死宣。當(dāng)調(diào)用線程的 interrupt()方法時(shí)伟恶,會拋出 InterruptException 異常。阻塞中的那個(gè)方法拋出這個(gè)異常毅该,通過代碼捕獲該異常博秫,然后 break 跳出循環(huán)狀態(tài),從而讓我們有機(jī)會結(jié)束這個(gè)線程的執(zhí)行眶掌。通常很多人認(rèn)為只要調(diào)用 interrupt 方法線程就會結(jié)束挡育,實(shí)際上是錯的, 一定要先捕獲 InterruptedException 異常之后通過 break 來跳出循環(huán)朴爬,才能正常結(jié)束 run 方法即寒。
2. 線程未處于阻塞狀態(tài):使用 isInterrupted()判斷線程的中斷標(biāo)志來退出循環(huán)。當(dāng)使用 interrupt()方法時(shí),中斷標(biāo)志就會置 true母赵,和使用自定義的標(biāo)志來控制循環(huán)是一樣的道理逸爵。
4. stop 方法終止線程(線程不安全)
程序中可以直接使用 thread.stop()來強(qiáng)行終止線程,但是 stop 方法是很危險(xiǎn)的凹嘲,就象突然關(guān) 閉計(jì)算機(jī)電源师倔,而不是按正常程序關(guān)機(jī)一樣,可能會產(chǎn)生不可預(yù)料的結(jié)果周蹭,不安全主要是: thread.stop()調(diào)用之后趋艘,創(chuàng)建子線程的線程就會拋出 ThreadDeatherror 的錯誤,并且會釋放子 線程所持有的所有鎖凶朗。一般任何進(jìn)行加鎖的代碼塊致稀,都是為了保護(hù)數(shù)據(jù)的一致性,如果在調(diào)用 thread.stop()后導(dǎo)致了該線程所持有的所有鎖的突然釋放(不可控制)俱尼,那么被保護(hù)數(shù)據(jù)就有可能呈 現(xiàn)不一致性抖单,其他線程在使用這些被破壞的數(shù)據(jù)時(shí),有可能導(dǎo)致一些很奇怪的應(yīng)用程序錯誤遇八。因此矛绘,并不推薦使用 stop 方法來終止線程。
5. sleep 與 wait 區(qū)別
(1). 對于 sleep()方法刃永,我們首先要知道該方法是屬于 Thread 類中的货矮。而 wait()方法,則是屬于Object 類中的斯够。13/04/2018 Page 62 of 283
(2). sleep()方法導(dǎo)致了程序暫停執(zhí)行指定的時(shí)間囚玫,讓出 cpu 該其他線程,但是他的監(jiān)控狀態(tài)依然保持者读规,當(dāng)指定的時(shí)間到了又會自動恢復(fù)運(yùn)行狀態(tài)抓督。
(3). 在調(diào)用 sleep()方法的過程中,線程不會釋放對象鎖束亏。
(4). 而當(dāng)調(diào)用 wait()方法的時(shí)候铃在,線程會放棄對象鎖,進(jìn)入等待此對象的等待鎖定池碍遍,只有針對此對象調(diào)用 notify()方法后本線程才進(jìn)入對象鎖定池準(zhǔn)備獲取對象鎖進(jìn)入運(yùn)行狀態(tài)定铜。
6. start 與 run 區(qū)別
(1). start()方法來啟動線程,真正實(shí)現(xiàn)了多線程運(yùn)行怕敬。這時(shí)無需等待 run 方法體代碼執(zhí)行完畢揣炕,可以直接繼續(xù)執(zhí)行下面的代碼。
(2). 通過調(diào)用 Thread 類的 start()方法來啟動一個(gè)線程东跪, 這時(shí)此線程是處于就緒狀態(tài)畸陡, 并沒有運(yùn)行矮烹。
(3). 方法 run()稱為線程體,它包含了要執(zhí)行的這個(gè)線程的內(nèi)容罩锐,線程就進(jìn)入了運(yùn)行狀態(tài)奉狈,開始運(yùn)行 run 函數(shù)當(dāng)中的代碼。 Run 方法運(yùn)行結(jié)束涩惑, 此線程終止仁期。然后 CPU 再調(diào)度其它線程。
歡迎大家點(diǎn)贊關(guān)注 轉(zhuǎn)發(fā)評論一起來討論竭恬。會每天給大家?guī)硪坏絻蓚€(gè)知識點(diǎn)跛蛋,一起成長