個人主頁:兜里有顆棉花糖
歡迎 點(diǎn)贊?? 收藏? 留言? 加關(guān)注??本文由 兜里有顆棉花糖 原創(chuàng)
收錄于專欄【Java系列專欄】【JaveEE學(xué)習(xí)專欄】
本專欄旨在分享學(xué)習(xí)Java的一點(diǎn)學(xué)習(xí)心得,歡迎大家在評論區(qū)交流討論??
一、啟動一個線程-start()方法
在操作系統(tǒng)中創(chuàng)建線程時,通常會同時創(chuàng)建相應(yīng)的PCB并將其加入到線程管理的數(shù)據(jù)結(jié)構(gòu)中,比如線程鏈表或線程隊列(此步驟是由操作系統(tǒng)內(nèi)核來完成的)。
調(diào)用 start 方法, 才真的在操作系統(tǒng)的底層創(chuàng)建出一個線程
。解釋:
start 方法會通過調(diào)用系統(tǒng)API
向操作系統(tǒng)請求創(chuàng)建一個新的線程,并分配相應(yīng)的資源。這個請求將由操作系統(tǒng)內(nèi)核處理,內(nèi)核將為線程分配所需的資源。至于線程什么時候創(chuàng)建完成是由操作系統(tǒng)內(nèi)核說了算的
。
一旦操作系統(tǒng)內(nèi)核完成資源的分配和初始化,線程就被創(chuàng)建出來了。此時,操作系統(tǒng)會將線程狀態(tài)設(shè)置為就緒狀態(tài),表示該線程已經(jīng)準(zhǔn)備好被調(diào)度執(zhí)行。線程調(diào)度器將在適當(dāng)?shù)臅r機(jī)選擇一個就緒的線程,并將其分配給可用的CPU核心來執(zhí)行。調(diào)用start方法是在操作系統(tǒng)底層創(chuàng)建一個線程的觸發(fā)點(diǎn)。
另外,start方法的執(zhí)行是在一瞬間就被執(zhí)行完成的。因?yàn)閟tart方法這是負(fù)責(zé)向操作系統(tǒng)中請求創(chuàng)建出一個線程,start方法執(zhí)行完成之后,代碼就會立即執(zhí)行start方法后續(xù)的代碼邏輯。
二、終止一個線程(重點(diǎn))
我們知道,run方法執(zhí)行完畢之后線程就結(jié)束了。這里我們說的終止線程就是想辦法讓run方法快速執(zhí)行完畢(正常情況下run方法沒有執(zhí)行完的話線程是不會突然就結(jié)束了的,除非是特別極端的特殊情況,比如拔電源)。
(
方式一
)手動設(shè)置標(biāo)志位:
我們可以通過手動設(shè)定標(biāo)志位的方式來讓run方法快速執(zhí)行完畢。
舉例代碼如下:
public class Demo08 {
public static boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!isQuit) {
System.out.println("hello thread!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
// 主線程這里執(zhí)行一些其它的代碼邏輯
Thread.sleep(3000);
// 修改前面設(shè)定的標(biāo)志位
isQuit = true;
System.out.println("t線程至此終止!!!");
}
}
運(yùn)行結(jié)果如下:
上述代碼的執(zhí)行結(jié)果其實(shí)是不穩(wěn)定的,有時候可能會打印出來4個hello thread!!!
因?yàn)?code>sleep操作是存在誤差的,比如如果真實(shí)的sleep時間比3000毫秒多的話,這里可能打印出來的就是4個hello thread!!!
;如果這里真實(shí)的sleep時間比3000毫秒少的話,可能這里打印出來的就可能是兩個hello thread!!!
。
lambda變量捕獲
現(xiàn)在將上述的代碼進(jìn)行細(xì)微的更改,請看:
為什么這里的isQuit變量
如果寫作成員變量就不會報錯,如果寫作局部局部變量就會報錯呢?
上述的錯誤原因是由于lambda變量捕獲的原因,下面我們來進(jìn)行解釋:Lambda 表達(dá)式可以在其定義所在的方法的上下文中訪問和使用外部變量,即Lambda表達(dá)式可以捕獲外部變量。被Lambda表達(dá)式捕獲的外部變量必須是final或是事實(shí)上final的。
上述代碼中寫的lambda表達(dá)式相當(dāng)于一個回調(diào)函數(shù),它的執(zhí)行時機(jī)是在一個線程被創(chuàng)建好了之后,在另外一個線程內(nèi)部被調(diào)用執(zhí)行的。所以,lambda表達(dá)式的執(zhí)行時機(jī)會稍后一些。
所以這就導(dǎo)致后續(xù)我們執(zhí)行l(wèi)ambda表達(dá)式的時候,局部變量isQuit
已經(jīng)被銷毀了(isQuit局部變量是跟隨main方法的),換句話來說main線程已經(jīng)結(jié)束了,但是lambda表達(dá)式依然是在繼續(xù)執(zhí)行的。
上述這種情況是客觀存在的,但是如果讓lambda表達(dá)式去訪問一個已經(jīng)銷毀了的變量這顯然是不合適的。所以lambda表達(dá)式引入了變量捕獲這樣的機(jī)制
。
lambda表達(dá)式的變量捕獲機(jī)制:lambda表達(dá)式其實(shí)并不是直接訪問的外部的變量,而是將外部的變量進(jìn)行復(fù)制,即復(fù)制到lambda表達(dá)式內(nèi)部(這樣就解決了變量生命周期的問題)。lambda表達(dá)式的變量捕獲是有條件限制的:Lambda表達(dá)式內(nèi)部訪問的外部變量必須是final或者事實(shí)上是final(effectively final)。
事實(shí)上是final的(即effectively final)意思就是變量可以不被final修飾,但是在代碼中我們不能修改此變量,此時我們也可以稱該變量是final effectively。如果對某變量進(jìn)行修改的話,此時lambda表達(dá)式就不能對改變量進(jìn)行變量捕獲
(之所以java中這樣設(shè)定變量捕獲是因?yàn)閖ava是通過復(fù)制的方式來實(shí)現(xiàn)變量捕獲機(jī)制的,如果外邊的變量更改了但是lambda表達(dá)式內(nèi)部的變量沒有修改的話此時就很容易對代碼產(chǎn)生歧義)。
相比之下,JS語言實(shí)現(xiàn)變量捕獲的機(jī)制跟java中是有所區(qū)別的,JS并不是通過復(fù)制的方式來實(shí)現(xiàn)變量捕獲的,而是通過直接改變外部變量的生命周期來保證lambda表達(dá)式可以訪問到外部的變量,因此JS中的變量捕獲的變量沒有final或者effectively final的限制
以上是手動設(shè)置標(biāo)志位的方式,其實(shí)在Thread類中已經(jīng)為我們提供好了標(biāo)志位:
Thread標(biāo)志位
,下面來看代碼演示:
public class Demo09 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
// 這里的Thread.currentThread()其實(shí)就是t引用
// lambda表達(dá)式是在t構(gòu)造之間就被定義好的。所以編譯器構(gòu)造的lambda表達(dá)式看到t之后就會認(rèn)為t引用是一個還沒有初始化的對象。
while(!Thread.currentThread().isInterrupted()) {
System.out.println("hello world!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
Thread.sleep(3000);
// 這里是把標(biāo)志位設(shè)置為true
t.interrupt();
}
}
解釋:
while(Thread.currentThread().isInterrupted())
Thread.currentThread()可以獲取到當(dāng)前線程的對象,currentThread方法
是Thread類提供的一個靜態(tài)方法,在哪個線程中調(diào)用這個方法我們就可以獲得哪個線程對象的引用。isInterrupted()
是Thread類
提供的一個標(biāo)志位,如果為true的話代表線程應(yīng)該要結(jié)束;如果為false的話代表線程可以不必結(jié)束。isInterrupted()用于檢查當(dāng)前線程是否被中斷,并返回一個布爾值。如果線程沒有被中斷,該方法將返回false;如果線程被中斷,該方法將返回true。
。
代碼運(yùn)行結(jié)果如下:
代碼拋出異常后會繼續(xù)打印hello world!!!
,然后循環(huán)下去。
代碼解釋:代碼中的t線程陷入睡眠之后又被interrupt喚醒了;如果我們手動設(shè)置標(biāo)志位的話,是沒有辦法喚醒t線程
的。
當(dāng)一個線程正在睡眠(通過調(diào)用Thread.sleep()
方法)時,如果其他線程調(diào)用了該線程的interrupt()方法,會導(dǎo)致正在睡眠的線程被強(qiáng)制中斷,拋出InterruptedException異常。注意:上述代碼中,interrupt()方法是由主線程調(diào)用的。(當(dāng)一個線程A需要中斷另一個線程B時,它可以調(diào)用線程B的 interrupt() 方法。這個調(diào)用會將線程B的中斷狀態(tài)標(biāo)志位設(shè)置為 true,即表示線程B被請求中斷。)
在上述代碼中,當(dāng)t線程被interrupt()方法喚醒時(舉個栗子,比如我們設(shè)定的sleep(1000),但是此時才過去10毫秒,但是線程依然會被喚醒),它的中斷標(biāo)志位會被設(shè)置為true。而sleep()方法會拋出InterruptedException異常,同時會清除中斷標(biāo)志位。
因此,在上述代碼中,當(dāng)sleep()方法被interrupt()方法喚醒時,它會拋出InterruptedException異常,并清除中斷標(biāo)志位。因此,前面設(shè)置的標(biāo)志位會被清除。此時,中斷標(biāo)志位會被重新設(shè)置為false
。當(dāng)中斷標(biāo)志位被重新設(shè)置為false之后,while循環(huán)會繼續(xù)進(jìn)行打印操作。
重點(diǎn):上述的代碼大家一定要好好進(jìn)行理解,尤其是中斷標(biāo)志位那個地方,有很多的小點(diǎn)需要大家注意。
上述代碼中,sleep被喚醒的同時,中斷標(biāo)志位被重新設(shè)定為了
false
;之后,線程會繼續(xù)執(zhí)行下去,但是如果我們想要讓線程結(jié)束的話,此時我們只需要在catch之后加上break就可以了。演示代碼如下:
運(yùn)行結(jié)果如下:
異常信息打印出來之后,代碼中的while循環(huán)就會被break,即使我們不清除標(biāo)志位的話,代碼依然就會結(jié)束(當(dāng)然加上break之后,標(biāo)志位依然會被清除然后標(biāo)志位會被重新設(shè)置為false)。
還有一點(diǎn)就是如果我們不想看到程序運(yùn)行結(jié)果中的異常信息的話,我們可以直接注釋掉catch中的e.printStackTrace();
(用于打印異常的堆棧跟蹤信息
)就好了
關(guān)于sleep喚醒之后可以執(zhí)行哪些操作:
我們這里依然是以剛剛上述的代碼進(jìn)行舉例,如果你忘記了的話,請看下圖:
sleep被喚醒之后,開發(fā)人員一般可以有以下幾種操作方式(給開發(fā)者留下了一定的操作空間,具體要干什么還是要根據(jù)具體的時機(jī)需求來決定):
- 立即結(jié)束線程(如上圖就是加上break之后就會立即結(jié)束線程)
- 執(zhí)行其它的一些代碼邏輯,執(zhí)行完這些代碼邏輯之后再結(jié)束線程(即再
catch
中執(zhí)行執(zhí)行其它的代碼邏輯,等到這些代碼邏輯執(zhí)行結(jié)束之后再break就可以了) - 或者忽略終止請求繼續(xù)循環(huán)下去(即
catch
中不寫break就好了)
判斷中斷標(biāo)志位的兩種方式:
- Thread.interrupted():這是一個靜態(tài)方法,
此方法在判定標(biāo)志位的同時會對標(biāo)志位進(jìn)行清除
- Thread.currentThread().isInterrupted():這是一個成員方式(
推薦使用這種方式
)判斷標(biāo)志位的時候不會進(jìn)行清除
三、等待一個線程-join()
我們知道多個線程是并發(fā)執(zhí)行的,具體的執(zhí)行過程都是由操作系統(tǒng)進(jìn)行調(diào)度的,而操作系統(tǒng)調(diào)度線程的過程是完全隨機(jī)的,隨機(jī)意味著我們不知道這些線程的執(zhí)行的先后順序是怎樣的。而這里的等待線程
就是用來規(guī)劃線程結(jié)束順序的手段
舉個栗子:現(xiàn)在又A、B兩個線程,我們希望B線程先結(jié)束而A線程后結(jié)束,所以可以讓A線程中調(diào)用B.jion()的方法,此時B線程沒有執(zhí)行完的話,那么A線程就會進(jìn)入阻塞狀態(tài)(
阻塞狀態(tài)的意思就是代碼不繼續(xù)往后執(zhí)行了,即該線程暫時不去cpu上參與調(diào)度
)。相當(dāng)于給B線程留下了執(zhí)行時間,當(dāng)B線程執(zhí)行完畢(即run方法執(zhí)行完畢)之后,A線程就會從阻塞狀態(tài)中恢復(fù)回來繼續(xù)往后執(zhí)行。
請看代碼演示:
public class Demo10 {
public static void main(String[] args) {
Thread b = new Thread(() -> {
for(int i = 0;i < 5;i++) {
System.out.println("hello b線程!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("b線程結(jié)束了?。。?);
});
Thread a = new Thread(() ->{
for(int i = 0;i < 3;i++) {
System.out.println("hello a線程!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("a線程結(jié)束了!!!");
});
b.start();
a.start();
}
}
代碼演示結(jié)果如下:
上述代碼中是a線程先結(jié)束b線程后結(jié)束。
現(xiàn)在我們讓b線程先結(jié)束a線程后結(jié)束的話,代碼如下圖:
運(yùn)行結(jié)果如下:
如上圖b線程先結(jié)束,a線程后結(jié)束。
關(guān)于阻塞阻塞狀態(tài)的解釋:阻塞狀態(tài)就是線程代碼不在繼續(xù)往后執(zhí)行了,即該線程不再參與cpu調(diào)度了。
sleep方法可以讓線程進(jìn)入阻塞狀態(tài),但是sleep方法的阻塞是有時間限制的。
而join方法的阻塞可以說是沒有時間限制,如果有兩個線程A、B,倘若是B.join()的話,如果B線程沒有執(zhí)行結(jié)束的話,那么A線程就會死等下去即A線程將永遠(yuǎn)不被執(zhí)行知道B線程執(zhí)行結(jié)束。
顯然,jion方法的死等這樣的方式是有些不大合適的,jion方法還有另外一種形式,即public void join(long millis)
(等待線程結(jié)束,最多等millis毫秒),這里的參數(shù)相當(dāng)于一個最大等待時間。
另外還有一點(diǎn):join方法是可以被interrupt方法喚醒的,其實(shí)
sleep、join、wait方法
產(chǎn)生阻塞之后都是可以被interrupt喚醒的(這幾個方法在被喚醒之后會自動清除標(biāo)志位,這一點(diǎn)和sleep類似)。
四、獲取當(dāng)前對象的引用
獲取當(dāng)前對象引用可以使用該方法:public static Thread currentThread();
(可以返回當(dāng)前線程對象的引用),在哪個線程中調(diào)用該方法就可以獲取到哪個線程的引用。
五、休眠當(dāng)前線程
休眠當(dāng)前線程方法如下:
public static void sleep(long millis) throws InterruptedException
:休眠當(dāng)前線程millis
毫秒public static void sleep(long millis, int nanos) throws InterruptedException
:可以更高精度的休眠
好了,本文到這里就結(jié)束了,希望友友們可以支持一下一鍵三連哈。嗯,就到這里吧,再見啦?。?!文章來源:http://www.zghlxwxcb.cn/news/detail-764859.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-764859.html
到了這里,關(guān)于【Java系列】詳解多線程(二)——Thread類及常見方法(下篇)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!