線(xiàn)程的狀態(tài)
萬(wàn)事萬(wàn)物都有其自己的生命周期和狀態(tài),一個(gè)線(xiàn)程從創(chuàng)建到結(jié)束被銷(xiāo)毀也有其自己的六種狀態(tài),而wait、notify、sleep等等這些方法就是協(xié)助切換線(xiàn)程間的狀態(tài)
Oracle官方文檔提供的六種線(xiàn)程狀態(tài)
| 狀態(tài)名稱(chēng) | 說(shuō)明 |
|---|---|
| NEW | 初始狀態(tài),線(xiàn)程被創(chuàng)建,但是還沒(méi)有調(diào)用start()方法,線(xiàn)程還未被啟動(dòng) |
| RUNNABLE | 運(yùn)行狀態(tài),一個(gè)線(xiàn)程開(kāi)始在java虛擬機(jī)中被執(zhí)行 |
| BLOCKED | 阻塞狀態(tài),線(xiàn)程被鎖住等待獲得對(duì)象的monitor lock,換言之就是被鎖(Synchronize)阻塞了 |
| WAITING | 等待狀態(tài),無(wú)限期等待另一個(gè)線(xiàn)程執(zhí)行特定操作的線(xiàn)程處于此狀態(tài)。 |
| TIMED_WAITING | 超時(shí)等待狀態(tài),在指定的等待時(shí)間內(nèi)等待另一個(gè)線(xiàn)程執(zhí)行操作的線(xiàn)程處于此狀態(tài)。 |
| TERMINATED | 終止?fàn)顟B(tài),線(xiàn)程執(zhí)行完畢已經(jīng)退出 |
用一張圖可以清晰的表示上述狀態(tài)在線(xiàn)程中的運(yùn)行狀態(tài)切換

線(xiàn)程的狀態(tài)切換的操作
建立線(xiàn)程后我們會(huì)根據(jù)需求對(duì)線(xiàn)程進(jìn)行一些操作,這些操作會(huì)改變線(xiàn)程的基本狀態(tài),同事也成為了線(xiàn)程間的一種通信方式,下面就主要聊聊這些方法。
-
wait()、notify()和notifyAll()
wait方法主要是將當(dāng)前運(yùn)行的線(xiàn)程掛起,讓其進(jìn)入阻塞狀態(tài),然后釋放它持有的同步鎖(也就是前面文章提到的monitor),通知其他線(xiàn)程來(lái)獲取執(zhí)行,直到notify和notifyAll方法來(lái)喚醒。wait也是一個(gè)多參數(shù)方法,可以通過(guò)wait(long timeout)來(lái)設(shè)定線(xiàn)程在指定時(shí)間內(nèi)如果沒(méi)有notify和notifyAll方法的喚醒,也會(huì)自動(dòng)喚醒,wait方法調(diào)用的也是這個(gè)方法,不過(guò)傳入的參數(shù)為0L。在使用
wait方法時(shí),一定要在同步范圍內(nèi),否則就會(huì)拋出IllegalMonitorStateException異常。
public class SynchronizedDemo {
public static void main(String[] args) {
final SynchronizedDemo test = new SynchronizedDemo();
new Thread(new Runnable() {
@Override
public void run() {
test.waitDemo();
}
}).start();
}
private void waitDemo() {
System.out.println("Start Thread"+System.currentTimeMillis());
try {
wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End Thread"+System.currentTimeMillis());
}
}
運(yùn)行結(jié)果:
Start Thread1557818387416
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at com.example.javalib.SynchronizedDemo.waitDemo(SynchronizedDemo.java:24)
at com.example.javalib.SynchronizedDemo.access$000(SynchronizedDemo.java:10)
at com.example.javalib.SynchronizedDemo$1.run(SynchronizedDemo.java:16)
at java.lang.Thread.run(Thread.java:745)
查看API文檔對(duì)于IllegalMonitorStateException的定義
Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor.
該錯(cuò)誤的大意為:線(xiàn)程試圖等待一個(gè)對(duì)象的監(jiān)視器或者去通知其他在等待對(duì)象監(jiān)視器的線(xiàn)程,但是該線(xiàn)程本身沒(méi)有持有指定的監(jiān)視器.主要是因?yàn)檎{(diào)用wait方法時(shí)沒(méi)有獲取到對(duì)象的monitor,獲得的途徑可以通過(guò)Synchronized關(guān)鍵字來(lái)完成,在上述代碼的方法中添加Synchronized關(guān)鍵字
private synchronized void waitDemo() {
System.out.println("Start Thread"+System.currentTimeMillis());
try {
wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End Thread"+System.currentTimeMillis());
}
通過(guò)這個(gè)例子得知,wait方法的使用必須在同步的范圍內(nèi),否則就會(huì)拋出IllegalMonitorStateException異常,wait方法的作用就是阻塞當(dāng)前線(xiàn)程等待notify/notifyAll方法的喚醒,或等待超時(shí)后自動(dòng)喚醒。
wait方法通過(guò)釋放對(duì)象的monitor來(lái)掛起線(xiàn)程,進(jìn)入WaitSet隊(duì)列, 然后后續(xù)等待鎖線(xiàn)程繼續(xù)來(lái)執(zhí)行,直到同一對(duì)象上調(diào)用notify或notifyAll后才可以喚醒等待線(xiàn)程。
notify 和 notifyAll的區(qū)別是notify方法只喚醒一個(gè)等待(對(duì)象的)線(xiàn)程并使該線(xiàn)程開(kāi)始執(zhí)行,如果有多個(gè)線(xiàn)程等待一個(gè)對(duì)象,那么只會(huì)隨機(jī)喚醒其中一個(gè)線(xiàn)程,后者則會(huì)喚醒所有等待(對(duì)象的)線(xiàn)程,哪個(gè)線(xiàn)程第一個(gè)被喚醒也是取決于操作系統(tǒng)。
負(fù)責(zé)調(diào)用方法去喚醒線(xiàn)程的線(xiàn)程也被稱(chēng)為喚醒線(xiàn)程,喚醒線(xiàn)程后不能被立刻執(zhí)行,因?yàn)閱拘丫€(xiàn)程還持有該對(duì)象的同步鎖,必須等待喚醒線(xiàn)程執(zhí)行完畢后釋放了對(duì)象的同步鎖后,等待線(xiàn)程才能獲取到對(duì)象的同步鎖進(jìn)而繼續(xù)執(zhí)行。
從上述中可以看到wait,notify,notifyAll方法的調(diào)用去掛起喚醒線(xiàn)程主要是操作對(duì)象的monitor,而monitor是所有對(duì)象的對(duì)象頭里都擁有的,所以這三個(gè)方法定義在Object類(lèi)中,而不是Thread類(lèi)中
下面一個(gè)用經(jīng)典面試題:雙線(xiàn)程打印奇偶數(shù)來(lái)展示wait和notify的用法(代碼隨便寫(xiě)的,理會(huì)意思就行)
public class Main {
Object odd = new Object(); // 奇數(shù)條件鎖
Object even = new Object(); // 偶數(shù)條件鎖
private int max=200;
private AtomicInteger status = new AtomicInteger(0); // AtomicInteger保證可見(jiàn)性,也可以用volatile
public Main() {
}
public static void main(String[] args) {
Main main = new Main();
Thread printer1 = new Thread(main.new MyPrinter("線(xiàn)程1", 0));
Thread printer2 = new Thread(main.new MyPrinter("線(xiàn)程2", 1));
printer1.start();
printer2.start();
}
public class MyPrinter2 implements Runnable {
private String name;
private int type; // 打印的類(lèi)型,0:代表打印奇數(shù),1:代表打印偶數(shù)
public MyPrinter2(String name, int type) {
this.name = name;
this.type = type;
}
@Override
public void run() {
ThreadBean bean = new ThreadBean();
bean.start(name);
}
}
public class MyPrinter implements Runnable {
private String name;
private int type; // 打印的類(lèi)型,0:代表打印奇數(shù),1:代表打印偶數(shù)
public MyPrinter(String name, int type) {
this.name = name;
this.type = type;
}
@Override
public void run() {
if (type == 0){
while(status.get()<20){
if(status.get()%2==0){
synchronized (even){
try {
even.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}else{
synchronized (odd){
System.out.println("當(dāng)前是"+name+"輸出"+status.get());
status.set(status.get()+1);
odd.notify();
}
}
}
}else{
while(status.get()<20){
if(status.get()%2==1){
synchronized (odd){
try {
odd.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}else{
synchronized (even){
System.out.println("當(dāng)前是"+name+"輸出"+status.get());
status.set(status.get()+1);
even.notify();
}
}
}
}
}
}
}
-
yield
yield是一個(gè)靜態(tài)的原生native方法,他的作用是讓出當(dāng)前線(xiàn)程的CPU分配的時(shí)間片,將其分配給和當(dāng)前線(xiàn)程同優(yōu)先級(jí)的線(xiàn)程,然后當(dāng)前線(xiàn)程狀態(tài)由運(yùn)行中(RUNNING)轉(zhuǎn)換為可運(yùn)行(RUNNABLE)狀態(tài),但這個(gè)并不是等待或者阻塞狀態(tài),也不會(huì)釋放對(duì)象鎖,如果在下一次競(jìng)爭(zhēng)中,又獲得了CPU時(shí)間片當(dāng)前線(xiàn)程依然會(huì)繼續(xù)運(yùn)行。
現(xiàn)在的操作系統(tǒng)中包含多個(gè)進(jìn)程,一個(gè)進(jìn)程又包含多個(gè)線(xiàn)程,那么這些多線(xiàn)程是一起執(zhí)行的嗎?就像電腦上,我們可以一邊看電視一邊瀏覽網(wǎng)頁(yè),其實(shí)并不然,看視兩邊同步進(jìn)行的,但其實(shí)是cpu讓兩個(gè)線(xiàn)程交替執(zhí)行,只不過(guò)交替執(zhí)行的速度很快,肉眼分辨不出來(lái),所以才會(huì)有同步執(zhí)行的錯(cuò)覺(jué)。同理,這里也是一樣,系統(tǒng)會(huì)分出一個(gè)個(gè)時(shí)間片,線(xiàn)程會(huì)被分配到屬于自己執(zhí)行的時(shí)間片,當(dāng)前線(xiàn)程的時(shí)間片用完后會(huì)等待下次分配,線(xiàn)程分配的時(shí)間多少也覺(jué)得了線(xiàn)程使用多少處理器的資源,線(xiàn)程優(yōu)先級(jí)也就是覺(jué)得線(xiàn)程是分配多一些還是少一些處理器的資源
Java中,通過(guò)一個(gè)整型變量Priority來(lái)控制線(xiàn)程的優(yōu)先級(jí),范圍為1~10,通過(guò)調(diào)用setPriority(int Priority)可以設(shè)置,默認(rèn)值為5。
同yield一樣,sleep也調(diào)用時(shí)也會(huì)交出當(dāng)前線(xiàn)程的處理器資源,但是不同的是sleep交出的資源所有線(xiàn)程都可以去競(jìng)爭(zhēng),yield交出的時(shí)間片資源只有和當(dāng)前線(xiàn)程同優(yōu)先級(jí)的線(xiàn)程才可以獲取到。
-
join
join方法的作用是父線(xiàn)程(一般是main主線(xiàn)程)等待子線(xiàn)程執(zhí)行完成后再執(zhí)行,換言之就是講異步執(zhí)行的線(xiàn)程合并為同步的主線(xiàn)程,。
同wait一樣,join方法也有多個(gè)參數(shù)的方法,也可以設(shè)定超時(shí)時(shí)間,join()方法調(diào)用的也是join(0L)
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("主線(xiàn)程開(kāi)始"+"時(shí)間:"+System.currentTimeMillis());
JoinDemo main = new JoinDemo();
Thread printer1 = new Thread(main.new MyPrinter("線(xiàn)程1"));
Thread printer2 = new Thread(main.new MyPrinter("線(xiàn)程2"));
Thread printer3 = new Thread(main.new MyPrinter("線(xiàn)程3"));
printer1.start();
printer1.join();
printer2.start();
printer2.join();
printer3.start();
System.out.println("主線(xiàn)程結(jié)束"+"時(shí)間:"+System.currentTimeMillis());
}
public class MyPrinter implements Runnable {
String content;
public MyPrinter(String content) {
this.content = content;
}
@Override
public void run() {
System.out.println("當(dāng)前線(xiàn)程"+content+"時(shí)間:"+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
輸出結(jié)果:
主線(xiàn)程開(kāi)始時(shí)間:1557824674063
當(dāng)前線(xiàn)程線(xiàn)程1時(shí)間:1557824674063
當(dāng)前線(xiàn)程線(xiàn)程2時(shí)間:1557824675065
主線(xiàn)程結(jié)束時(shí)間:1557824676065
當(dāng)前線(xiàn)程線(xiàn)程3時(shí)間:1557824676065
從上面例子可以看到線(xiàn)程1和2調(diào)用了join方法后,主線(xiàn)程是等待兩個(gè)線(xiàn)程執(zhí)行完成之后才會(huì)繼續(xù)執(zhí)行
-
interrupt
interrupt的目的是為了中斷線(xiàn)程,原來(lái)Thread.stop, Thread.suspend, Thread.resume 都有這個(gè)功能,但由于都太暴力了而被廢棄了,暴力中斷線(xiàn)程是一種不安全的操作,相對(duì)而言interrupt通過(guò)設(shè)置標(biāo)志位的方式就比較溫柔
interrupt基于一個(gè)線(xiàn)程不應(yīng)該由其他線(xiàn)程來(lái)強(qiáng)制中斷或停止,而是應(yīng)該由線(xiàn)程內(nèi)部來(lái)自行停止的思想來(lái)實(shí)現(xiàn)的,自己的事自己處理,是一種比較溫柔和安全的做法,而且中斷不活動(dòng)的線(xiàn)程不會(huì)產(chǎn)生任何影響。
從API文檔的中的介紹來(lái)看interrupt()的作用是中斷本線(xiàn)程。除非當(dāng)前線(xiàn)程正在中斷自身(始終允許),否則將調(diào)用此線(xiàn)程的checkAccess方法,但這可能導(dǎo)致拋出SecurityException。
如果在調(diào)用Object類(lèi)的wait()、join()、sleep(long)阻塞了這個(gè)線(xiàn)程,那么它的中斷狀態(tài)將被清除并收到InterruptedException。
如果在InterruptibleChannel上的I / O操作中阻塞了該線(xiàn)程,則該通道將被關(guān)閉,線(xiàn)程的中斷狀態(tài)將被設(shè)置,并且線(xiàn)程將收到ClosedByInterruptException。
-
終止阻塞線(xiàn)程
例如,線(xiàn)程通過(guò)wait()進(jìn)入阻塞狀態(tài),此時(shí)通過(guò)interrupt()中斷該線(xiàn)程;調(diào)用interrupt()會(huì)立即將線(xiàn)程的中斷標(biāo)記設(shè)為“true”,但是由于線(xiàn)程處于阻塞狀態(tài),所以該“中斷標(biāo)記”會(huì)立即被清除為“false”,同時(shí),會(huì)產(chǎn)生一個(gè)InterruptedException的異常。此時(shí)將InterruptedException放在適當(dāng)?shù)奈恢眠M(jìn)行捕獲就能終止阻塞中的線(xiàn)程,如下代碼,將中斷的捕獲放在while(true)之外,就可以退出while循環(huán)
@Override
public void run() {
try {
while (true) {
// 執(zhí)行任務(wù)...
}
} catch (InterruptedException ie) {
// 由于產(chǎn)生InterruptedException異常,退出while(true)循環(huán),線(xiàn)程終止!
}
}
但是如果需要將··InterruptedException··在··while(true)``循環(huán)體之內(nèi)的話(huà),就需要額外的添加退出處理,通過(guò)捕獲異常后的break退出當(dāng)前循環(huán)。
@Override
public void run() {
while (true) {
try {
// 執(zhí)行任務(wù)...
} catch (InterruptedException ie) {
// InterruptedException在while(true)循環(huán)體內(nèi)。
// 當(dāng)線(xiàn)程產(chǎn)生了InterruptedException異常時(shí),while(true)仍能繼續(xù)運(yùn)行!需要手動(dòng)退出
break;
}
}
}
-
終止運(yùn)行線(xiàn)程
通常,我們通過(guò)“標(biāo)記”方式終止處于“運(yùn)行狀態(tài)”的線(xiàn)程。其中,包括“中斷標(biāo)記”和“額外添加標(biāo)記”。通過(guò)設(shè)立一個(gè)標(biāo)志來(lái)在線(xiàn)程運(yùn)行的時(shí)候判斷是否執(zhí)行下去。
@Override
public void run() {
while (!isInterrupted()) {
}
}
isInterrupted是Thread的內(nèi)部方法,可以獲取當(dāng)前線(xiàn)程是否中斷的標(biāo)志,當(dāng)線(xiàn)程處于運(yùn)行狀態(tài)時(shí),我們通過(guò)interrupt()修改線(xiàn)程的中斷標(biāo)志,來(lái)達(dá)到退出while循環(huán)的作用。
上述是系統(tǒng)內(nèi)部的標(biāo)志符號(hào),我們也可以自己設(shè)置一個(gè)標(biāo)志符來(lái)達(dá)到退出線(xiàn)程的作用
private volatile boolean isExit= false;
protected void exitThread() {
isExit= true;
}
@Override
public void run() {
while (isExit) {
}
}
通過(guò)自己設(shè)置標(biāo)志符,在需要的時(shí)候直接調(diào)用exitThread就可以修改while的判斷條件,從而達(dá)到退出線(xiàn)程的目的。
綜合阻塞和運(yùn)行狀態(tài)下線(xiàn)程的終止方式,結(jié)合兩者可以使用一個(gè)通用較為安全的方法
@Override
public void run() {
try {
// 1. isInterrupted()保證,只要中斷標(biāo)記為true就終止線(xiàn)程。
while (!isInterrupted()) {
}
} catch (InterruptedException ie) {
// 2. InterruptedException異常保證,當(dāng)InterruptedException異常產(chǎn)生時(shí),線(xiàn)程被終止。
}
}
最后談?wù)?interrupted()和 isInterrupted()。
interrupted()和 isInterrupted()都能夠用于檢測(cè)對(duì)象的“中斷標(biāo)記”。
區(qū)別是,interrupted()除了返回中斷標(biāo)記之外,它還會(huì)清除中斷標(biāo)記(即將中斷標(biāo)記設(shè)為false);而isInterrupted()僅僅返回中斷標(biāo)記。
-
Sleep
最后簡(jiǎn)單說(shuō)一下sleep,這算是多線(xiàn)程我們最常用的方法了
sleep是Thread的靜態(tài)native方法,它的作用是讓當(dāng)前線(xiàn)程按照指定的時(shí)間休眠,休眠時(shí)期線(xiàn)程不會(huì)釋放鎖,但是會(huì)讓出執(zhí)行當(dāng)前線(xiàn)程的cpu資源給其他線(xiàn)程使用,和wait較為類(lèi)似,但是也有一些不同點(diǎn)。
-
sleep()是Thread的靜態(tài)內(nèi)部方法,wait()是object類(lèi)的方法 -
wait()方法必須在同步代碼塊中使用,必須獲得對(duì)象鎖(monitor),sleep()方法則可以再仍和地方中使用,wait()方法會(huì)釋放當(dāng)前占有的對(duì)象鎖,本身進(jìn)入waitset隊(duì)列,等待被喚醒,sleep()方法只會(huì)讓出cpu資源,并不會(huì)釋放鎖 -
sleep()方法在休眠時(shí)間結(jié)束后獲得CPU分配的資源后就可以繼續(xù)執(zhí)行,wait()方法需要被notify()喚醒后還需要等待喚醒線(xiàn)程執(zhí)行完畢釋放鎖后,才會(huì)獲得CPU資源繼續(xù)執(zhí)行
-
線(xiàn)程的狀態(tài)轉(zhuǎn)換以及基本操作
Java 并發(fā)編程:線(xiàn)程間的協(xié)作
Java多線(xiàn)程系列--“基礎(chǔ)篇”09之 interrupt()和線(xiàn)程終止方式