一诉位、前言
本文介紹Java線程相關(guān)知識(不包括線程同步+線程通信骑脱,這個內(nèi)容在筆者的另一篇博客中介紹過了),包括:線程生命周期苍糠、線程優(yōu)先級叁丧、線程禮讓、后臺線程岳瞭、聯(lián)合線程拥娄。
二、線程生命周期
2.1 引子:線程生命周期
本節(jié)闡述線程生命周期相關(guān)知識瞳筏,Java支持多線程技術(shù)稚瘾,除了Main函數(shù)主導(dǎo)一個main線程以外,可以用代碼創(chuàng)建一系列的前臺線程姚炕、后臺線程(本文后面會講)摊欠,每一個線程都有自己的生命周期,線程生命周期的不同狀態(tài)有不同的說法:
(1)有的說Java線程5種狀態(tài)柱宦,這是因為將“等待狀態(tài)Waiting+限時等待狀態(tài)Timed_Waiting”作為一種狀態(tài)些椒,5種狀態(tài)為:
新建狀態(tài)New、可運行狀態(tài)Runnable(Running+Ready)掸刊、等待狀態(tài)Waiting+Timed_Waiting免糕、阻塞狀態(tài)Blocked、結(jié)束狀態(tài)Terminated
(2)有的說Java線程6種狀態(tài),這是因為將將Running和Ready兩種狀態(tài)拆分開了石窑,6種狀態(tài)為:
新建狀態(tài)New牌芋、準(zhǔn)備狀態(tài)Ready、運行狀態(tài)Running尼斧、等待狀態(tài)Waiting+Timed_Waiting姜贡、阻塞狀態(tài)Blocked试吁、結(jié)束狀態(tài)Terminated
或者將等待狀態(tài)Waiting和限時等待狀態(tài)Timed_Waiting兩種狀態(tài)拆開棺棵,6種狀態(tài)為:
新建狀態(tài)New、可運行狀態(tài)Runnable(Running+Ready)熄捍、等待狀態(tài)Waiting烛恤、計時等待狀態(tài)Timed_Waiting、阻塞狀態(tài)Blocked余耽、結(jié)束狀態(tài)Terminated
(3)有的說Java線程7種狀態(tài)缚柏,這是因為將將Running和Ready兩種狀態(tài)拆分開、等待狀態(tài)Waiting和限時等待狀態(tài)Timed_Waiting兩種狀態(tài)拆開碟贾,7種狀態(tài)為:
新建狀態(tài)New币喧、準(zhǔn)備狀態(tài)Ready、運行狀態(tài)Running袱耽、 等待狀態(tài)Waiting杀餐、計時等待狀態(tài)Timed_Waiting、阻塞狀態(tài)Blocked朱巨、結(jié)束狀態(tài)Terminated
不管采用哪種說法史翘,Java線程狀態(tài)以下幾種,新建狀態(tài)New冀续、可運行狀態(tài)Runnable(Running+Ready)琼讽、等待狀態(tài)(等待狀態(tài)Waiting+限時等待狀態(tài)Timed_Waiting)、阻塞狀態(tài)Blocked洪唐、結(jié)束狀態(tài)Terminated
本文采用6種狀態(tài)的說法钻蹬,下面2.3 會具體闡述。
2.2 用一段代碼來演示一個完整的生命周期
代碼1:
package mypackage;
// 線程的生命周期凭需,演示線程的六種狀態(tài): NEW 新建狀態(tài) RUNNABLE 可運行狀態(tài)(ready就緒狀態(tài)+running運行狀態(tài)) TIMED_WAITING 計時等待狀態(tài)
// WAITING 等待狀態(tài) BLOCKED 阻塞狀態(tài) TERMINATED 終止?fàn)顟B(tài)(進(jìn)入終止?fàn)顟B(tài)后只能結(jié)束问欠,無法再返回回來)
class Block { //共享資源類
public boolean waitStatus = true;
public void waitOp() throws InterruptedException {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " 進(jìn)入wait");
wait();
// waitThread被notifyThread喚醒
System.out.println(Thread.currentThread().getName() + " next to Block.wait(), in the loop");
}
}
public void notifyOp() {
synchronized (this) {
this.waitStatus = false; // 修改標(biāo)志位 等待狀態(tài)waitStatus=false
notifyAll(); // 喚醒WaitRunnable
System.out.println(Thread.currentThread().getName() + " 向正在wait當(dāng)前對象的線程發(fā)出notify");
}
}
}
class WaitThread implements Runnable {
private Block block;
public WaitThread(Block block) {
super();
this.block = block;
}
@Override
public void run() {
//該方法中測試了 sleep(毫秒數(shù))進(jìn)入計時等待狀態(tài)和wait()進(jìn)入等待狀態(tài)
// 前者只能等待2秒后自動喚醒 后者由notifyThread使用notify()/notifyAll()喚醒
try {
System.out.println(Thread.currentThread().getName() + " 被啟動,開始執(zhí)行...");
// Thread.sleep(毫秒數(shù)) 進(jìn)入計時等待狀態(tài) 線程計時等待狀態(tài)中功炮,線程依然存活溅潜,線程isAlive() 仍然為true
System.out.println(Thread.currentThread().getName() + " 開始sleep 2s");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " 從sleep中醒來");
// 從sleep中醒來后,進(jìn)入wait狀態(tài)
while (block.waitStatus) {
block.waitOp(); // 進(jìn)入等待狀態(tài)薪伏,線程在等待狀態(tài)中滚澜,線程依然存活,線程isAlive()
// 仍然為true嫁怀,等待狀態(tài)只有一個出口设捐,notify()/notifyAll()
// waitThread被notifyThread喚醒
System.out.println(Thread.currentThread().getName() + " next to WaitRunnable.waitOp(), in the loop");
}
System.out.println(Thread.currentThread().getName() + " out of wait loop");
System.out.println(Thread.currentThread().getName() + " 任務(wù)執(zhí)行結(jié)束借浊,即將終止...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class NotifyThread implements Runnable {
private Block block;
public NotifyThread(Block block) {
super();
this.block = block;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 被啟動,開始執(zhí)行...");
block.notifyOp();
synchronized (block) {
try {
System.out.println(Thread.currentThread().getName() + " 開始sleep 6s萝招,并且不會釋放當(dāng)前對象的鎖");
// 進(jìn)入計時等待狀態(tài)有兩種方式 wait(毫秒數(shù)) sleep(毫秒數(shù)) 兩者不同在于
// wait(毫秒數(shù))釋放鎖 ,其他線程(本程序中另一個線程)獲得鎖并運行
// sleep(毫秒數(shù))不釋放鎖,其他線程(本程序中另一個線程)不可以獲得鎖并運行
// 現(xiàn)在notifyThread sleep(6000) 不釋放鎖蚂斤,所以waitThread也不能運行
// ,只能等到6秒后notifyThread醒來
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// notifyThread醒來了,打印一下
System.out.println(Thread.currentThread().getName() + " 從sleep中醒來");
}
}
public class Test {
private static final String IN_THE_STATE = "當(dāng)前的狀態(tài)為:";
private static final String ISALIVE_RETRUN = "isAlive()返回為:";
public static void printStatus(Thread thread) {
System.out.println(thread.getName() + " " + IN_THE_STATE + thread.getState().toString() + " " + ISALIVE_RETRUN
+ thread.isAlive());
}
public static void main(String[] args) throws InterruptedException {
// 該對象將作為后面加鎖的對象
Block block = new Block();
// new Thread() 進(jìn)入新建狀態(tài)
Thread waitThread = new Thread(new WaitThread(block));
Thread notifyThread = new Thread(new NotifyThread(block));
printStatus(waitThread); // 打印當(dāng)前狀態(tài)槐沼,當(dāng)前狀態(tài)為新建狀態(tài) 線程尚未啟動曙蒸,isAlive()為false
printStatus(notifyThread);// 打印當(dāng)前狀態(tài),當(dāng)前狀態(tài)為新建狀態(tài) 線程尚未啟動岗钩,isAlive()為false
// .start() 進(jìn)入可運行狀態(tài) (包括就緒狀態(tài)和運行狀態(tài))
// 就緒狀態(tài)進(jìn)入運行狀態(tài)是CPU調(diào)度纽窟,程序員無法控制,所本程序中對這兩種狀態(tài)不做區(qū)分兼吓,統(tǒng)一為可運行狀態(tài)
waitThread.start();
printStatus(waitThread); // 打印當(dāng)前狀態(tài)臂港,當(dāng)前狀態(tài)為可運行狀態(tài) 線程啟動,isAlive()為true
Thread.sleep(1000);
// TIMED_WAITING
printStatus(waitThread); // 打印當(dāng)前狀態(tài)视搏,當(dāng)前狀態(tài)為計時等待狀態(tài)
Thread.sleep(2000);
// WAITING
printStatus(waitThread); // 打印當(dāng)前狀態(tài)审孽,當(dāng)前狀態(tài)為wait()等待狀態(tài),等待被notifyThread()喚醒才能繼續(xù)下去
Thread.sleep(1000); // 主線程main線程等待1s
notifyThread.start(); // 啟動notify線程,去喚醒處于等待狀態(tài)的waitThread
printStatus(notifyThread); // 打印notifyThread當(dāng)前狀態(tài)浑娜,當(dāng)前狀態(tài)為可運行狀態(tài)
// 線程啟動佑力,isAlive()為true
Thread.sleep(1000); // 主線程main線程等待1s
// BLOCKED or TERMINATED
// TERMINATED:運行到此處時,如果notifyThread執(zhí)行完notify操作后棚愤,調(diào)度器立馬切換至thread的情況下搓萧,thread會先行終止,調(diào)度器再調(diào)度notifyThread
printStatus(waitThread); // 現(xiàn)在處于notifyThread sleep(6000) 睡眠6秒過程中 我們打印一下被notifyThread喚醒的waitThread處于什么狀態(tài)
// waitThread為Block狀態(tài),islive為true
//這是因為waitThread雖然被notifyThread喚醒宛畦,從等待狀態(tài)中出來瘸洛,但是因為notifyThread現(xiàn)在一直占用同步鎖,waitThread無法獲得同步鎖次和,所以處于阻塞狀態(tài)
//等待狀態(tài)和阻塞狀態(tài)區(qū)別:Java中反肋,每個對象都有兩個池,鎖池和等待池踏施,
//阻塞狀態(tài)兩種:I/O阻塞(本程序不涉及石蔗,略)和獲取同步鎖失敗而阻塞(本程序當(dāng)前情況),線程對象處于鎖池畅形,鎖池的中線程對象獲取到同步鎖之后就可以進(jìn)入可運行狀態(tài)運行养距,即阻塞狀態(tài)的線程獲取同步鎖成功后就可以運行
//等待和計時等待狀態(tài):sleep(毫秒數(shù))只能等待自動喚醒,wait(毫秒數(shù))可以等待自動喚醒或notify()/notifyAll()喚醒日熬,wait()只能notify()/notifyAll()喚醒棍厌,三種等待都是處于等待池中
//wait()和wait(毫秒數(shù))釋放同步鎖,其間不占用同步鎖,sleep(毫秒數(shù))占用同步鎖耘纱,其間不釋放同步鎖
printStatus(notifyThread); // 現(xiàn)在處于notifyThread sleep(6000) 睡眠6秒過程中
// notifyThread 為計時等待狀態(tài),islive為true
Thread.sleep(6000); // 主線程睡眠6s 讓兩個子程序有足夠的時間進(jìn)入終止?fàn)顟B(tài)
// 終止?fàn)顟B(tài) 主線程給了6s 足夠進(jìn)入終止?fàn)顟B(tài)了
printStatus(waitThread); // waitThread 進(jìn)入終止?fàn)顟B(tài)敬肚,isAlive()為false
printStatus(notifyThread); // notifyThread 進(jìn)入終止?fàn)顟B(tài),isAlive()為false
// 注意:線程一旦進(jìn)入終止?fàn)顟B(tài)束析,只能結(jié)束艳馒,無法再返回來
}
}
輸出1:
Thread-0 當(dāng)前的狀態(tài)為:NEW isAlive()返回為:false
Thread-1 當(dāng)前的狀態(tài)為:NEW isAlive()返回為:false
Thread-0 當(dāng)前的狀態(tài)為:RUNNABLE isAlive()返回為:true
Thread-0 被啟動,開始執(zhí)行...
Thread-0 開始sleep 2s
Thread-0 當(dāng)前的狀態(tài)為:TIMED_WAITING isAlive()返回為:true
Thread-0 從sleep中醒來
Thread-0 進(jìn)入wait
Thread-0 當(dāng)前的狀態(tài)為:WAITING isAlive()返回為:true
Thread-1 當(dāng)前的狀態(tài)為:RUNNABLE isAlive()返回為:true
Thread-1 被啟動员寇,開始執(zhí)行...
Thread-1 向正在wait當(dāng)前對象的線程發(fā)出notify
Thread-1 開始sleep 6s弄慰,并且不會釋放當(dāng)前對象的鎖
Thread-0 當(dāng)前的狀態(tài)為:BLOCKED isAlive()返回為:true
Thread-1 當(dāng)前的狀態(tài)為:TIMED_WAITING isAlive()返回為:true
Thread-1 從sleep中醒來
Thread-0 next to Block.wait(), in the loop
Thread-0 next to WaitRunnable.waitOp(), in the loop
Thread-0 out of wait loop
Thread-0 任務(wù)執(zhí)行結(jié)束,即將終止...
Thread-0 當(dāng)前的狀態(tài)為:TERMINATED isAlive()返回為:false
Thread-1 當(dāng)前的狀態(tài)為:TERMINATED isAlive()返回為:false
小結(jié):本程序(已經(jīng)注釋的比較清楚了丁恭,這里不再贅余)使用synchronized+標(biāo)志位waitStatus+wait()+notify()/notifyAll()曹动,程序中的兩個線程waitThread和notifyThread,既使用到了線程同步+線程通信牲览,又監(jiān)聽了六個狀態(tài),幫助讀者很好的理解Java線程生命周期六個狀態(tài)恶守。
2.3 線程生命周期的相關(guān)問題
問題(1):線程有幾種狀態(tài)第献?狀態(tài)之間如何轉(zhuǎn)換?
回答(1):沒有絕對的答案兔港,本文采用Java官方的解釋庸毫,6種狀態(tài),分別是 :NEW 新建狀態(tài) RUNNABLE 可運行狀態(tài)(包括ready就緒狀態(tài)+running運行狀態(tài)) TIMED_WAITING 計時等待狀態(tài)
WAITING 等待狀態(tài) BLOCKED 阻塞狀態(tài) TERMINATED 終止?fàn)顟B(tài)(進(jìn)入終止?fàn)顟B(tài)后只能結(jié)束衫樊,無法再返回回來)
關(guān)于狀態(tài)之間的轉(zhuǎn)換圖飒赃,我認(rèn)為這張圖最能符合標(biāo)準(zhǔn):
補(bǔ)充:因為可運行狀態(tài)Runnable包括Ready就緒狀態(tài)和Running運行狀態(tài),但是就緒狀態(tài)進(jìn)入運行狀態(tài)是CPU調(diào)度科侈,程序員無法控制载佳,所本文中對這兩種狀態(tài)不做區(qū)分,統(tǒng)一為可運行狀態(tài)臀栈,兩者中轉(zhuǎn)換如圖:
問題(2):六種狀態(tài)isAlive()返回值蔫慧?
回答(2):新建狀態(tài)和終止?fàn)顟B(tài)isAlive()為false,表示線程此時不存活;其他四種狀態(tài)isAlive()為true权薯,表示線程此時為存活姑躲。
問題(3):進(jìn)入終止?fàn)顟B(tài)terminate還可以再回到可運行狀態(tài)嗎?
回答(3):不可以盟蚣,進(jìn)入終止?fàn)顟B(tài)線程只能死亡黍析,不可以再回到可運行狀態(tài)。
問題(4):等待狀態(tài)(包括計時等待)和阻塞狀態(tài)的區(qū)別屎开?wait()阐枣、wait(毫秒數(shù))和sleep(毫秒數(shù))區(qū)別?
回答(4):等待狀態(tài)和阻塞狀態(tài)區(qū)別:Java中,每個對象都有兩個池侮繁,鎖池和等待池虑粥,
阻塞狀態(tài)兩種:
I/O阻塞(本程序不涉及,略)和獲取同步鎖失敗而阻塞(本程序當(dāng)前情況)宪哩,線程對象處于鎖池娩贷,鎖池的中線程對象獲取到同步鎖之后就可以進(jìn)入可運行狀態(tài)運行,即阻塞狀態(tài)的線程獲取同步鎖成功后就可以運行锁孟;
等待和計時等待狀態(tài):
進(jìn)入等待狀態(tài)(包括計時等待)方式有三種(sleep(毫秒數(shù))彬祖、wait()、wait(毫秒數(shù)))
sleep(毫秒數(shù))只能等待自動喚醒品抽,wait(毫秒數(shù))可以等待自動喚醒或notify()/notifyAll()喚醒储笑,wait()只能notify()/notifyAll()喚醒,三種等待都是處于等待池中圆恤。
另外突倍,wait()和wait(毫秒數(shù))釋放同步鎖,其間不占用同步鎖盆昙,sleep(毫秒數(shù))占用同步鎖羽历,其間不釋放同步鎖
方法名 | 線程狀態(tài) | 喚醒 | 狀態(tài)期間 |
---|---|---|---|
wait() | 進(jìn)入等待狀態(tài) | 只能notify()/notifyAll()喚醒 | 釋放同步鎖,其間不占用同步鎖 |
wait(毫秒數(shù)) | 進(jìn)入計時等待狀態(tài) | 可以等待自動喚醒或notify()/notifyAll()喚醒 | 釋放同步鎖淡喜,其間不占用同步鎖 |
sleep(毫秒數(shù)) | 進(jìn)入計時等待狀態(tài) | 只能等待自動喚醒 | 占用同步鎖秕磷,其間不釋放同步鎖 |
三、線程優(yōu)先級
Java中提供了操作線程優(yōu)先級的方法setter-getter炼团, int getPriority() :返回線程的優(yōu)先級澎嚣。 void setPriority(int newPriority) : 更改線程的優(yōu)先級。 另外瘟芝,提供了三個表現(xiàn)線程優(yōu)先級的常量易桃,
MAX_PRIORITY=10,最高優(yōu)先級
MIN_PRIORITY=1,最低優(yōu)先級
NORM_PRIORITY=5,默認(rèn)優(yōu)先級,
線程優(yōu)先級有1~ 10,10種模狭,程序員也可以直接使用數(shù)字1~10設(shè)置線程優(yōu)先級颈抚。線程優(yōu)先級代碼實現(xiàn)且見代碼1:
代碼——線程優(yōu)先級:
package mypackage;
public class SimplePriorities implements Runnable {
private int countDown = 5;
public SimplePriorities(int priority) {
Thread.currentThread().setPriority(priority);
}
public String toString() {
return Thread.currentThread() + ": " + countDown;
}
@Override
public void run() {
while (true) {
System.out.println(this);
if (--countDown == 0) {
return;
}
}
}
public static void main(String[] args) {
new Thread(new SimplePriorities(Thread.MIN_PRIORITY)).start();
new Thread(new SimplePriorities(Thread.MAX_PRIORITY)).start();
}
}
輸出:
Thread[Thread-1,10,main]: 5
Thread[Thread-1,10,main]: 4
Thread[Thread-1,10,main]: 3
Thread[Thread-0,1,main]: 5
Thread[Thread-1,10,main]: 2
Thread[Thread-0,1,main]: 4
Thread[Thread-1,10,main]: 1
Thread[Thread-0,1,main]: 3
Thread[Thread-0,1,main]: 2
Thread[Thread-0,1,main]: 1
小結(jié):我們看到,程序開始的時候嚼鹉,即使在客戶端低優(yōu)先級的線程Thread-0先創(chuàng)建和執(zhí)行贩汉,但是輸出結(jié)果中,高優(yōu)先級的線程Thread-1可以獲得更多執(zhí)行機(jī)會(前面5個锚赤,Thread-1打印了4個匹舞,Thread-0打印1個,后面5個沒有意義线脚,因為Thread-1已經(jīng)快打印完了赐稽,每個線程countDown總數(shù)只有5)(注意:打印結(jié)果不同且當(dāng)前線程數(shù)和countDown數(shù)字較小叫榕,特例沒有意義)
注意1:值得注意的是,線程優(yōu)先級并不是指線程執(zhí)行的先后順序姊舵,而是線程被執(zhí)行的概率權(quán)重晰绎。事實上,除非程序員使用標(biāo)志位做線程通信括丁,否則Java并沒有提供任何線程執(zhí)行先后順序的機(jī)制荞下,哪個線程先執(zhí)行只取決于CPU調(diào)度。
注意2:此外史飞,不同的操作系統(tǒng)支持的線程優(yōu)先級不同的,建議使用上述三個優(yōu)先級MAX_PRIORITY尖昏、MIN_PRIORITY、NORM_PRIORITY,不要自定義构资。
四抽诉、線程禮讓
線程禮讓是以線程優(yōu)先級為基礎(chǔ)的(本文先介紹線程優(yōu)先級,再介紹線程禮讓)吐绵,線程禮讓yield方法只會給相同優(yōu)先級或者更高優(yōu)先級的線程運行的機(jī)會. 且看如下代碼:
代碼——線程禮讓(在線程優(yōu)先級代碼的基礎(chǔ)上添加):
package mypackage_線程禮讓;
public class SimplePriorities implements Runnable {
private int countDown = 5;
public SimplePriorities(int priority) {
Thread.currentThread().setPriority(priority);
}
public String toString() {
return Thread.currentThread() + ": " + countDown;
}
@Override
public void run() {
while (true) {
Thread.yield();
System.out.println(this);
if (--countDown == 0) {
return;
}
}
}
public static void main(String[] args) {
new Thread(new SimplePriorities(Thread.MIN_PRIORITY)).start();
new Thread(new SimplePriorities(Thread.MAX_PRIORITY)).start();
}
}
輸出:
Thread[Thread-1,10,main]: 5
Thread[Thread-0,1,main]: 5
Thread[Thread-1,10,main]: 4
Thread[Thread-0,1,main]: 4
Thread[Thread-1,10,main]: 3
Thread[Thread-1,10,main]: 2
Thread[Thread-1,10,main]: 1
Thread[Thread-0,1,main]: 3
Thread[Thread-0,1,main]: 2
Thread[Thread-0,1,main]: 1
小結(jié):在run()方法中加上Thread.yield()之后迹淌,可以更大程度上保證高優(yōu)先級的線程打敗低優(yōu)先級的線程,因為yield()只能將線程禮讓給同等優(yōu)先級和更高優(yōu)先級的線程拦赠,本程序中Thread-0會禮讓給Thread-1,但是Thread-1不會禮讓給Thread-0,所以Thread.yield()本身更大程度上保證高優(yōu)先級的線程打敗低優(yōu)先級的線程(注意:不絕對巍沙,況且的這里的countDown計數(shù)器和客戶端線程數(shù)都很少)。
附:線程禮讓yield()和線程休眠sleep()的相同點和不同點
sleep方法和yield方法的區(qū)別:
1):相同點:都能使當(dāng)前處于運行狀態(tài)的線程放棄CPU,把運行的機(jī)會給其他線程.
2):不同點——轉(zhuǎn)讓運行機(jī)會的條件不同:sleep方法會給其他線程運行機(jī)會,但是不考慮其他線程的優(yōu)先級,yield方法只會給相同優(yōu)先級或者更高優(yōu)先級的線程運行的機(jī)會.
3):不同點——轉(zhuǎn)讓后進(jìn)入的狀態(tài)不同:調(diào)用sleep方法后,線程進(jìn)入計時等待狀態(tài),調(diào)用yield方法后,線程進(jìn)入就緒狀態(tài).
4):不同點——兩方法開發(fā)中用途不同:sleep()方法開發(fā)中更多的用于模擬延遲,讓多線程并發(fā)訪問同一個資源的錯誤效果更明顯.荷鼠;yield()方法開發(fā)中很少會使用到該方法,該方法主要用于調(diào)試或測試榔幸,它可能有助于因多線程競爭條件下的錯誤重現(xiàn)現(xiàn)象允乐。
五、后臺線程
5.1 引子:后臺線程
Java中削咆,前臺線程創(chuàng)建的線程默認(rèn)是前臺線程(可以通過setDaenon(true)方法設(shè)置為后臺線程)牍疏,后臺線程創(chuàng)建的線程默認(rèn)是后臺線程。由于main線程是一個前臺線程拨齐,所以我們新建的線程默認(rèn)都是前臺線程鳞陨,本節(jié)闡述Java線程另一種線程——后臺線程。
注意1:設(shè)置后臺線程:thread.setDaemon(true),該方法必須在start方法調(diào)用前瞻惋,否則出現(xiàn)IllegalThreadStateException異常厦滤。
注意2:前臺線程停止,后臺線程也停止歼狼,即使是finally塊的內(nèi)容也不執(zhí)行了掏导,且見本節(jié)代碼演示。
注意3:main線程是前臺線程
5.2 前臺線程與后臺線程
代碼1——前臺線程停止羽峰,后臺線程也停止:
package mypackage;
public class Test {
public static void main(String[] args) {
Thread daemonThread = new Thread(new DaemonThread());
daemonThread.setDaemon(true); // thread.setDaemon(true),該方法必須在start方法調(diào)用前趟咆,否則出現(xiàn)IllegalThreadStateException異常添瓷。
daemonThread.start();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(500);
// mainThread中每次休眠比后臺線程少,這個mainThread就比后臺線程先結(jié)束值纱,從而證明前臺線程結(jié)束后后臺線程也會結(jié)束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("This is mainThread");
}
}
}
class DaemonThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("This is DaemonThread");
}
}
}
輸出1:
This is mainThread
This is DaemonThread
This is mainThread
This is mainThread
This is DaemonThread
This is mainThread
This is mainThread
小結(jié)1:該程序(即代碼1)充分說明了鳞贷,前臺線程停止,那么它創(chuàng)建的后臺線程也停止虐唠,但是如果是后臺線程中finally塊里面的內(nèi)容搀愧,會任何時候都得到執(zhí)行嗎?且看代碼2凿滤。
代碼2——前臺線程停止妈橄,后臺線程也停止,即使是finally塊的內(nèi)容也不執(zhí)行了:
package mypackage2;
public class Test {
public static void main(String[] args) {
Thread daemonThread = new Thread(new DaemonThread());
daemonThread.setDaemon(true); // thread.setDaemon(true),該方法必須在start方法調(diào)用前翁脆,否則出現(xiàn)IllegalThreadStateException異常眷蚓。
daemonThread.start();
}
}
class DaemonThread implements Runnable {
@Override
public void run() {
try {
System.out.println("This is DaemonThread");
} finally {
System.out.println("This is finally Blocks");
}
}
}
輸出2:
This is DaemonThread
小結(jié)2:該程序(代碼2)有三種輸出情況:
第一,為空反番,什么都不輸出沙热,這是問題在后臺線程尚未打印This is DaemonThread,主線程就結(jié)束了罢缸;
第二篙贸,輸出為This is DaemonThread\n This is finally Blocks ,這是因為后臺線程完全打印完后,主線程才結(jié)束枫疆;
第三爵川,輸出為This is DaemonThread,這是因為后臺線程尚未執(zhí)行完息楔,finally中的代碼未執(zhí)行完寝贡,主線程結(jié)束。
我們重點談?wù)摰谌N輸出值依,這一個輸出可以充分說明圃泡,后臺線程中即使是finally塊中的語句,也不一定會得到執(zhí)行愿险。這與我們的認(rèn)知的是矛盾的颇蜡,通常來說,finally塊的語句一定會得到執(zhí)行辆亏,所以開發(fā)的時候一般將jdbc連接的close或io的close放在finally塊里面风秤。
原因簡單闡述,這是因為main()退出時褒链,JVM就會立即關(guān)閉所有的后臺進(jìn)場唁情,并不會有任何你希望出現(xiàn)的確認(rèn)形式。
解決方法甫匹,只能讓程序員自己來解決這個問題甸鸟,寫程序的時候惦费,要慎用后臺線程,如代碼2抢韭,若將main()中setDaemon()注釋掉薪贫,就永遠(yuǎn)可以看到finally子句的執(zhí)行,所有要慎用后臺線程刻恭,否則finally子句將不再安全瞧省。
5.3 后臺線程:小結(jié)
后臺線程最重要的點就是生命周期跟隨創(chuàng)建它的前臺線程,前臺線程死亡鳍贾,后臺線程立刻死亡鞍匾,即使是finally中的子句也不一定會執(zhí)行。
六骑科、聯(lián)合線程
6.1 引子:聯(lián)合線程
定義:Java中橡淑,聯(lián)合線程使用join()方法實現(xiàn),線程的join方法表示一個線程等待另一個線程完成后才執(zhí)行咆爽。join方法被調(diào)用之后梁棠,線程對象處于阻塞狀態(tài)。 有人也把這種方式稱為聯(lián)合線程斗埂,就是說把當(dāng)前線程和當(dāng)前線程所在的線程聯(lián)合成一個線程符糊,且見本節(jié)代碼。
6.2 聯(lián)合線程代碼演示
代碼1:
package mypackage;
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread joinThread = new Thread(new JoinThread());
for (int i = 0; i < 5; i++) {
System.out.println("main :" + i);
if (i == 1) {
joinThread.start();
}
if (i == 3) {
joinThread.join(); // 強(qiáng)制運行joinThread,main線程被設(shè)置為阻塞狀態(tài)
}
}
}
}
class JoinThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("JoinThread: " + i);
}
}
}
輸出1:
main :0
main :1
main :2
main :3
JoinThread: 0
JoinThread: 1
JoinThread: 2
JoinThread: 3
JoinThread: 4
main :4
小結(jié):join()方法就用阻塞當(dāng)前正在運行的線程(代碼1中為main線程)呛凶,運行新建加入的線程(代碼1中的JoinThread),等到JoinThread運行完之后再運行mian線程男娄。可以更加簡單的理解漾稀,就是在main線程中嵌入一個JoinThread線程沪伙。
6.3 聯(lián)合線程:小結(jié)
聯(lián)合線程就是在某線程(代碼1中main線程)中嵌入一個新線程(代碼1中JoinThread),然后運行的時候當(dāng)然是按照嵌入的運行县好。
七、小結(jié)
本文介紹Java線程相關(guān)知識暖混,包括:線程生命周期缕贡、線程優(yōu)先級、線程禮讓拣播、后臺線程晾咪、聯(lián)合線程,基本涵蓋Java多線程部分的各個常用知識點贮配,希望對初學(xué)者學(xué)習(xí)者提供幫助谍倦。
天天打碼,天天進(jìn)步泪勒!