(最近剛來到簡書平臺禀忆,以前在CSDN上寫的一些東西臊旭,也在逐漸的移到這兒來,有些篇幅是很早的時候寫下的箩退,因此可能會看到一些內(nèi)容雜亂的文章离熏,對此深感抱歉,以下為正文)
正文
本篇要講述的是線程中的啟動線程(start)戴涝,中斷線程(interrupt)滋戳,等待線程(join),以及線程睡眠(sleep)喊括。下面將分別介紹這四種線程操作方式胧瓜。
啟動線程
當我們創(chuàng)建好一個線程對象或者其子類對象后,我們可以通過調用Thread類中的start方法來啟動與該對象所關聯(lián)的線程郑什。下面用一個簡單的例子來進行示例
package com.newway.interruputtest;
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println("線程開始工作");
});
thread.start();
}
}
執(zhí)行上述代碼府喳,可以在控制臺看到如下打印:
如果調用start方法的線程本身已經(jīng)處于運行狀態(tài)蘑拯,或者該線程已經(jīng)運行結束處于死亡狀態(tài)钝满,那么此時將會拋出IllegalThreadStateException兜粘。從源碼中也可以看出端倪。
可以從源碼中看出弯蚜,當執(zhí)行start方法的時候孔轴,會先對當前線程的狀態(tài)進行檢測,這里有一個int型的標志量threadStatus碎捺,它定義于Thread類中路鹰,當且僅當線程最初新建的時候,該標志量初始化值為0收厨,當調用start方法時晋柱,如果該標志量不等于0,則會拋出IllegalThreadStateException,如果等于0诵叁,就繼續(xù)執(zhí)行后續(xù)的操作代碼雁竞,啟動thread對象相關聯(lián)的線程。
從上面的示例中可以看出拧额,當創(chuàng)建完一個線程碑诉,連續(xù)調用了兩次start方法,此時拋出相應異常侥锦。
中斷線程
Java提供了一種線程可以中斷線程的機制进栽。在這里我們要先明確一個概念,這里的線程中斷機制并不是說通過該機制可以主動的去終止一個線程的運行捎拯,它僅僅起到一個協(xié)作的作用泪幌。
Java中中斷機制的本質是Java在每一個線程對象中都設置一個boolean類型的標志量(該標志量并未定義在Thread類里,具體的操作都是由native方法來實現(xiàn)的)署照,該標志量代表了當前線程的中斷狀態(tài)祸泪,如果當前線程被中斷,該標志量會被設為true建芙,Java的中斷機制到此為止没隘,至于被中斷的線程是繼續(xù)執(zhí)行、終止亦或是捕捉中斷來進行特殊處理禁荸,這些都是由被中斷的線程來決定的右蒲。
當一個線程因為調用join、wait赶熟、sleep等方法造成阻塞的時候瑰妄,如果阻塞時間過長,那么此時可以調用中斷方法映砖,那么被中斷線程就會拋出java.lang.InterruptedException異常间坐,從而退出阻塞狀態(tài)并將中斷標志位清空設為false。
wait方法
join方法
sleep方法
此時我們可以通過捕獲異常的方式來進行針對處理(當然只捕捉而不做任何處理這種行為是不提倡的),也可以繼續(xù)向上拋出異常竹宋,此時可能需要重新調用interruput方法來將標志位設為true(如果需要的話)劳澄,因為拋出異常后當前線程的中斷狀態(tài)會被清空。
其實Thread類中早期是存在主動終止線程運行的方法的蜈七,有stop方法秒拔,還有suspend方法和resume方法,但正是因為他們可以隨意的終止線程飒硅,這樣存在著巨大的安全性隱患砂缩,所以如今這些方法都已經(jīng)被棄用了。比如在A線程中調用B線程的stop方法狡相,但是A線程并不知道B線程的運行狀態(tài)梯轻,如果B線程此時仍然在運行自己的操作代碼,結果A線程終止了它尽棕,那么B線程的任務就無法完成,假設B線程在做著I/O操作彬伦,那么此時便造成了數(shù)據(jù)的丟失滔悉。
Thread類中提供了三個方法來支持該機制:
void interruput():調用該方法后,會中斷調用此方法的線程對象所關聯(lián)的系統(tǒng)線程单绑。其實本質就是將線程中的中斷標志位設為true回官,這也是唯一一個可以將中斷標志位置為true的方法。
static boolean interrupted():此方法用于驗證線程是否已經(jīng)中斷搂橙。同時線程的中斷狀態(tài)會被這個方法清除掉(使用時需謹慎)歉提。也就是說當連續(xù)調用兩次這個方法的時候,必然會得到false的結果区转。該方法一般用于當接收到中斷信號后程序進行捕捉處理繼續(xù)運行苔巨,之后仍可能接收到中斷信號,所以在判斷中斷標志的同時需要將標志位清空废离,置為false侄泽。
boolean isInterrupted:此方法用于驗證線程是否已經(jīng)中斷。該方法同上面的interrupted方法功能類似蜻韭,但是不會影響線程的中斷狀態(tài)悼尾。
下面來說說線程中斷的一些應用場景:
- 某應用正在執(zhí)行某些操作,此時點擊取消按鈕肖方,例如使用電腦管家在進行全盤掃描闺魏,掃描過程中點擊了停止掃描按鈕。
- 線程阻塞時間過長俯画,已經(jīng)無需繼續(xù)等待下去析桥。
- 多線程同時在做一件事情,如果其中某個線程完成,則其它線程就無需繼續(xù)工作烹骨。
- 當應用或服務需要停止時翻伺。
- …
下面就結合一些小例子來加深對線程中斷的理解:
1. 對于Thread類提供的3個支持中斷機制的方法的驗證:
package com.newway.interruputtest;
public class InterruputTest1 {
Thread t = null;
public static void main(String[] args) throws Exception {
new InterruputTest1().test();
}
public void test() throws Exception {
try {
t = new Thread(() -> {
System.out.println("------thread t is working------");
for(;;){
System.out.println("thread t isInterruputed ? "+t.isInterrupted());
System.out.println("thread t interruputed ? "+Thread.interrupted());
System.out.println("------thread t invoke method interrupted------");
long time = System.currentTimeMillis();
while(System.currentTimeMillis() - time < 1000){}
}
});
} catch (Exception e) {
e.printStackTrace();
}
t.start();
Thread.sleep(100);
System.out.println("------thread t invoke method interrupt------");
t.interrupt();
}
}
執(zhí)行上述代碼后可以在控制臺上看到如下打印:
在本例中沮焕,隨著方法的執(zhí)行吨岭,我們創(chuàng)建了一個線程t,線程中通過一個死循環(huán)來不斷的調用interrupted和isInterrupted方法來打印線程t的中斷狀態(tài)峦树。
線程創(chuàng)建運行辣辫,可以看出打印中通過兩個方法得到的中斷狀態(tài)都為false。
緊接著在主線程中魁巩,通過線程t的對象調用了interrupt方法急灭,此時通過兩個方法得到的中斷狀態(tài)都為true,證實了interrupt方法確實可以將線程的中斷狀態(tài)設為true谷遂。然后程序繼續(xù)執(zhí)行葬馋,這里體現(xiàn)了中斷線程并不會終止線程的運行,因為本例中未對線程中斷做任何處理肾扰,所以調用interrupt中斷后畴嘶,線程繼續(xù)執(zhí)行。
線程t繼續(xù)執(zhí)行操作代碼集晚,因為在上一個循環(huán)末尾處窗悯,調用了isInterrupted方法,前面說過偷拔,該方法在獲取線程中斷狀態(tài)后蒋院,會清空調用線程的中斷狀態(tài),所以當再次循環(huán)打印線程中斷狀態(tài)后莲绰,兩個方法得出的結果都是false欺旧。
第一個例子主要是為了描述這三個方法到底是干什么用的,下面就是結合一些小場景來說說怎么用钉蒲。
2.可中斷的阻塞方法與中斷機制:
前面我們說過當使用sleep切端,join,wait等會造成線程阻塞的方法的時候顷啼,如果當前線程調用了interrupt方法踏枣,發(fā)出中斷信號,那么此時線程會拋出InterruptedException钙蒙。
這里會存在兩種情況茵瀑,一種是先調用了sleep等阻塞方法,在阻塞過程中調用了interrupt方法躬厌,還有一種則是先行調用了interrupt方法马昨,然后當執(zhí)行sleep等阻塞方法的時候會立馬拋出InterruptedException竞帽。下面通過兩個小例子來演示這兩種情況。
第一種情況:先阻塞后中斷
第一個例子在主線程中創(chuàng)建了一個子線程t鸿捧,子線程中調用了sleep方法睡眠2000ms屹篓。主線程隨后調用start方法啟動線程t,然后睡眠500ms匙奴,此處是為了確保子線程進入sleep狀態(tài)堆巧。主線程睡眠結束后,調用interrupt方法泼菌,中斷t線程谍肤,從控制臺可以看出,t線程拋出了InterruptedException哗伯,在sleep的睡眠時間結束之前就打破了阻塞荒揣。
第二種情況:先中斷后阻塞
第二個例子就比較簡單了,直接使用主線程來模擬焊刹,在主線程中先行調用interrupt方法發(fā)送中斷信號系任,然后調用sleep方法,從控制臺的輸出可以看出虐块,當調用sleep的時候赋除,主線程立馬拋出了InterruptedException,描述是睡眠中斷非凌。
綜上所述,當我們遇到了一些可中斷的阻塞方法時荆针,可以通過Thread類中的interrupt方法來產(chǎn)生中斷敞嗡,從而打斷阻塞。
3.通過中斷機制來終止線程的運行:
前面我們有說過中斷機制僅僅只是起到協(xié)作的作用航背,至于被中斷的線程在中斷之后的具體操作是什么喉悴,是由被中斷的線程自己來決定的。下面通過幾個小例子來展示如何通過中斷機制來終止線程的運行玖媚。
- 通過自定義中斷狀態(tài)來中斷一個線程的運行(有些情況下Thread類提供的三個支持中斷線程的方法已經(jīng)不能滿足我們的需求時箕肃,需要我們自定義中斷狀態(tài)來解決問題):
package com.newway.interruputtest;
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mt.interrupt();
mt.stop = true;
}
}
class MyThread extends Thread {
volatile boolean stop;
@Override
public void run() {
while(!stop){
long time = System.currentTimeMillis();
System.out.println("mythread is working...");
while(System.currentTimeMillis()-time < 1000){}
}
System.out.println("mythread is stoping...");
}
}
運行上述代碼可以得到如下打印:
示例中今魔,我們創(chuàng)建了一個MyThread類勺像,其繼承于java lang包下的Thread類。在類中我們定義了一個boolean型變量错森,作為我們自定義的中斷狀態(tài)吟宦,該變量使用了volatile關鍵字進行修飾,是為了保證其可見性涩维,關于該關鍵字在其它的篇幅中將會講述殃姓。MyThread類中還重寫了run方法,方法內(nèi)通過一個while循環(huán)來每隔一秒打印一次輸出語句,這里我們將自定義的中斷狀態(tài)量stop作為循環(huán)的控制變量蜗侈。
在主線程中篷牌,我們創(chuàng)建出MyThread類的對象,并調用其start方法啟動了它踏幻,主線程在睡眠了三秒之后調用其interrupt方法(這里只是為了表示這里要產(chǎn)生中斷)枷颊,并將stop的值設為了true,這樣線程中的循環(huán)打印將在下一個循環(huán)判斷中退出循環(huán)叫倍,從而終止了線程的運行偷卧。
- 通過Thread類提供的三個方法來中斷線程的運行:
package com.newway.interruputtest;
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mt.interrupt();
}
}
class MyThread extends Thread {
@Override
public void run() {
//這里同樣可以使用Thread.currentThread().interrupted()方法來判斷中斷,但是該方法會在判斷狀態(tài)之后清空中斷狀態(tài)位吆倦,在使用時應根據(jù)實際場景來選擇听诸。
while(!Thread.currentThread().isInterrupted()){
long time = System.currentTimeMillis();
System.out.println("mythread is working...");
while(System.currentTimeMillis()-time < 1000){}
}
System.out.println("mythread is stoping...");
}
}
執(zhí)行上述代碼后可以從控制臺看到如下打印:
該示例與第一個示例區(qū)別不大蚕泽,僅僅只是循環(huán)的控制變量從自定義的中斷狀態(tài)改為通過Thread類提供的isInterrupted方法獲取的線程中斷狀態(tài)晌梨。
乍一看下來,有了Thread類提供的方法后须妻,第一個例子中自定義中斷狀態(tài)的方法似乎變得不再需要了仔蝌,其實并不然,在實際的開發(fā)中荒吏,我們經(jīng)常需要使用比人的庫敛惊,如果別人的方法中自己處理了中斷并且沒有繼續(xù)向上通知,而你卻需要對中斷進行響應绰更,在你不能改動比人代碼的時候你就需要使用自定義的中斷狀態(tài)來實現(xiàn)自己的功能瞧挤。
- 阻塞狀態(tài)下對中斷的處理:
前面提到過,線程在調用可中斷的阻塞方法時儡湾,當調用其中斷方法后特恬,會拋出相應InterruptedException。當我捕獲到這些異常時徐钠,是不推薦對該異常進行空處理的癌刽,如:
package com.newway.interruputtest;
public class Test {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (;;) {
try {
Thread.sleep(500);
} catch (Exception e) {
}
System.out.println("thread t is working");
}
});
t.start();
t.interrupt();
}
}
執(zhí)行上述代碼后我們可以在控制臺看到如下打印:
事實上當我們調用interrupt方法時就已經(jīng)拋出了InterruptedException尝丐,但因為我們沒有對該異常進行任何處理显拜,這樣導致上層完全無法得知此處發(fā)生了中斷,所以一般情況下是不建議如此操作的(當然特殊情況下你自己了解這樣做不會對整體造成任何問題摊崭,你要這樣寫也不是不可以的讼油,比如本示例中在catch中進行空操作也沒有造成程序問題)。
一般情況下呢簸,當我們捕捉到了可中斷阻塞方法拋出的InterruptedException矮台,我們可以繼續(xù)向上層拋出該異常乏屯,如果是檢測到了中斷,我們則可以清空當前的中斷狀態(tài)同樣拋出該異常瘦赫,使我們自己的方法成為一個可中斷的方法辰晕。在一些不好拋出異常的情況下,可以調用Thread類提供的interrupt方法确虱,重置當前的中斷狀態(tài)含友,因為拋出異常時該狀態(tài)會被清空,重置后校辩,在其它需要對中斷進行操作的地方可以繼續(xù)通過判斷中斷標志位來繼續(xù)工作窘问。
關于中斷就說到這兒吧,下面貼出兩個專門寫線程中斷的帖子宜咒,筆者在學習中斷的時候惠赫,從中得到了很多幫助:
1.詳細分析java中斷機制
2.Thread中的中斷機制
等待線程
在一個線程中我們偶爾會啟動另一個線程進行一些操作,并要等待其返回一個結果后故黑,當前線程再繼續(xù)向下執(zhí)行儿咱。這時我們就需要使用到Thread類中提供的join方法。
Thread類中總共提供了3中join方法:
-
void join(long millis):調用線程在死亡之前最多等待millis毫秒场晶,如果傳入的值為0混埠,將無限等待下去,如果傳入millis為負數(shù)诗轻,則會拋出IllegalArgumentException钳宪。該方法是可中斷的,當?shù)却^程中遭遇到線程中斷時扳炬,該方法會拋出InterruptedException使套,并且清空中斷狀態(tài)。
-
void join(long millis, int nanos):調用線程在死亡之前最多等待millis毫秒nanos納秒鞠柄,如果傳入的參數(shù)包含負數(shù)或者nanos的值大于999999將會拋出IllegalArgumentException。該方法是可中斷的嫉柴,當?shù)却^程中遭遇到線程中斷時厌杜,該方法會拋出InterruptedException,并且清空中斷狀態(tài)计螺。
-
void join():調用線程在死亡之前將無限期的等待夯尽。該方法是可中斷的,當?shù)却^程中遭遇到線程中斷時登馒,該方法會拋出InterruptedException匙握,并且清空中斷狀態(tài)。
從源碼中可以看出陈轿,join方法的本質是通過wait方法來實現(xiàn)的圈纺,并且當且僅當當前線程是處于存活狀態(tài)時秦忿,該方法才有作用。我們可以看出該方法是同步方法蛾娶,意味著使用該方法的前提是調用者能拿到被調用者的鎖灯谣。下面我們通過一個簡單的小例子來展示join的使用方法。
package com.newway.interruputtest;
public class Test {
static Thread t,t1;
public static void main(String[] args) {
t = new Thread(()->{
System.out.println("thread t is working");
try {
t1.join();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("thread t has been finished");
});
t1= new Thread(()->{
System.out.println("thread t1 is working");
System.out.println("thread t1 has been finished");
});
t.start();
t1.start();
}
}
運行以上代碼可以在控制臺看到如下打踊桌拧:
在示例中胎许,我們在主線程中創(chuàng)建了兩個子線程t,t1罗售,并順序啟動了它們辜窑。在t線程的run方法中我們調用了t1對象的join方法,此時t線程將等待t1線程執(zhí)行完畢后才會繼續(xù)執(zhí)行寨躁,因此控制臺打印中“thread t has been finished”才在最后打印出來穆碎。
所以也有人將join比作可以將兩個交替執(zhí)行的線程合并為一個順序執(zhí)行的線程的工具。
線程睡眠
Thread類提供了一對靜態(tài)方法來使當前線程進入睡眠狀態(tài)朽缎,暫時性的停止執(zhí)行任務惨远。
- void sleep(long millis):使當前線程睡眠millis毫秒的時間。如果millis是負數(shù)话肖,則會拋出IllegalArgumentException北秽。該方發(fā)是可中斷的,如果在睡眠期間當前線程被中斷最筒,那么會拋出InterruptedException贺氓,并且清空當前線程的中斷狀態(tài)。
- void sleep(long millis,int nanos):使當前線程睡眠millis毫秒和nanos納秒的時間床蜘。如果傳入的參數(shù)存在負數(shù)辙培,或者nanos的值大于999999,那么會拋出IllegalArgumentException邢锯。該方發(fā)是可中斷的扬蕊,如果在睡眠期間當前線程被中斷,那么會拋出InterruptedException丹擎,并且清空當前線程的中斷狀態(tài)尾抑。
對于sleep方法的使用這里就不舉例描述了,在 前面的示例中已經(jīng)多次使用過該方法蒂培,該方法是靜態(tài)方法再愈,所以直接通過Thread類調用即可,并且操作的是當前線程护戳。
這里注意的是翎冲,sleep方法中傳入的參數(shù)是表示當前線程進入睡眠暫停執(zhí)行的最短時間,因為其結束睡眠后進入的是準備狀態(tài)媳荒,所以具體什么時候恢復操作還要看調度器是否立馬給予當前線程執(zhí)行的權限抗悍。
以上為本篇的全部內(nèi)容驹饺。