線程的狀態(tài)
萬(wàn)事萬(wàn)物都有其自己的生命周期和狀態(tài)讼稚,一個(gè)線程從創(chuàng)建到結(jié)束被銷(xiāo)毀也有其自己的六種狀態(tài)括儒,而wait、notify锐想、sleep等等這些方法就是協(xié)助切換線程間的狀態(tài)
狀態(tài)名稱(chēng) | 說(shuō)明 |
---|---|
NEW | 初始狀態(tài)帮寻,線程被創(chuàng)建,但是還沒(méi)有調(diào)用start()方法赠摇,線程還未被啟動(dòng) |
RUNNABLE | 運(yùn)行狀態(tài)固逗,一個(gè)線程開(kāi)始在java虛擬機(jī)中被執(zhí)行 |
BLOCKED | 阻塞狀態(tài),線程被鎖住等待獲得對(duì)象的monitor lock ,換言之就是被鎖(Synchronize)阻塞了 |
WAITING | 等待狀態(tài)藕帜,無(wú)限期等待另一個(gè)線程執(zhí)行特定操作的線程處于此狀態(tài)烫罩。 |
TIMED_WAITING | 超時(shí)等待狀態(tài),在指定的等待時(shí)間內(nèi)等待另一個(gè)線程執(zhí)行操作的線程處于此狀態(tài)洽故。 |
TERMINATED | 終止?fàn)顟B(tài)贝攒,線程執(zhí)行完畢已經(jīng)退出 |
用一張圖可以清晰的表示上述狀態(tài)在線程中的運(yùn)行狀態(tài)切換
線程的狀態(tài)切換的操作
建立線程后我們會(huì)根據(jù)需求對(duì)線程進(jìn)行一些操作,這些操作會(huì)改變線程的基本狀態(tài)收津,同事也成為了線程間的一種通信方式饿这,下面就主要聊聊這些方法浊伙。
-
wait()撞秋、notify()和notifyAll()
wait
方法主要是將當(dāng)前運(yùn)行的線程掛起,讓其進(jìn)入阻塞狀態(tài)嚣鄙,然后釋放它持有的同步鎖(也就是前面文章提到的monitor)吻贿,通知其他線程來(lái)獲取執(zhí)行,直到notify
和notifyAll
方法來(lái)喚醒哑子。wait
也是一個(gè)多參數(shù)方法舅列,可以通過(guò)wait(long timeout)
來(lái)設(shè)定線程在指定時(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ò)誤的大意為:線程試圖等待一個(gè)對(duì)象的監(jiān)視器或者去通知其他在等待對(duì)象監(jiā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)前線程等待notify/notifyAll
方法的喚醒,或等待超時(shí)后自動(dòng)喚醒规个。
wait
方法通過(guò)釋放對(duì)象的monitor
來(lái)掛起線程凤薛,進(jìn)入WaitSet
隊(duì)列姓建, 然后后續(xù)等待鎖線程繼續(xù)來(lái)執(zhí)行,直到同一對(duì)象上調(diào)用notify
或notifyAll
后才可以喚醒等待線程缤苫。
notify 和 notifyAll的區(qū)別是notify方法只喚醒一個(gè)等待(對(duì)象的)線程并使該線程開(kāi)始執(zhí)行引瀑,如果有多個(gè)線程等待一個(gè)對(duì)象,那么只會(huì)隨機(jī)喚醒其中一個(gè)線程榨馁,后者則會(huì)喚醒所有等待(對(duì)象的)線程憨栽,哪個(gè)線程第一個(gè)被喚醒也是取決于操作系統(tǒng)。
負(fù)責(zé)調(diào)用方法去喚醒線程的線程也被稱(chēng)為喚醒線程翼虫,喚醒線程后不能被立刻執(zhí)行屑柔,因?yàn)閱拘丫€程還持有該對(duì)象的同步鎖,必須等待喚醒線程執(zhí)行完畢后釋放了對(duì)象的同步鎖后珍剑,等待線程才能獲取到對(duì)象的同步鎖進(jìn)而繼續(xù)執(zhí)行掸宛。
從上述中可以看到wait,notify招拙,notifyAll方法的調(diào)用去掛起喚醒線程主要是操作對(duì)象的monitor
唧瘾,而monitor
是所有對(duì)象的對(duì)象頭里都擁有的,所以這三個(gè)方法定義在Object類(lèi)中别凤,而不是Thread類(lèi)中
下面一個(gè)用經(jīng)典面試題:雙線程打印奇偶數(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("線程1", 0));
Thread printer2 = new Thread(main.new MyPrinter("線程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)前線程的CPU分配的時(shí)間片,將其分配給和當(dāng)前線程同優(yōu)先級(jí)的線程杯巨,然后當(dāng)前線程狀態(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)前線程依然會(huì)繼續(xù)運(yùn)行。
現(xiàn)在的操作系統(tǒng)中包含多個(gè)進(jìn)程层扶,一個(gè)進(jìn)程又包含多個(gè)線程箫章,那么這些多線程是一起執(zhí)行的嗎?就像電腦上镜会,我們可以一邊看電視一邊瀏覽網(wǎng)頁(yè)檬寂,其實(shí)并不然,看視兩邊同步進(jìn)行的戳表,但其實(shí)是cpu讓兩個(gè)線程交替執(zhí)行桶至,只不過(guò)交替執(zhí)行的速度很快昼伴,肉眼分辨不出來(lái),所以才會(huì)有同步執(zhí)行的錯(cuò)覺(jué)镣屹。同理圃郊,這里也是一樣,系統(tǒng)會(huì)分出一個(gè)個(gè)時(shí)間片女蜈,線程會(huì)被分配到屬于自己執(zhí)行的時(shí)間片持舆,當(dāng)前線程的時(shí)間片用完后會(huì)等待下次分配,線程分配的時(shí)間多少也覺(jué)得了線程使用多少處理器的資源伪窖,線程優(yōu)先級(jí)也就是覺(jué)得線程是分配多一些還是少一些處理器的資源
Java中逸寓,通過(guò)一個(gè)整型變量Priority
來(lái)控制線程的優(yōu)先級(jí),范圍為1~10,通過(guò)調(diào)用setPriority(int Priority)
可以設(shè)置覆山,默認(rèn)值為5竹伸。
同yield
一樣,sleep
也調(diào)用時(shí)也會(huì)交出當(dāng)前線程的處理器資源簇宽,但是不同的是sleep
交出的資源所有線程都可以去競(jìng)爭(zhēng)勋篓,yield
交出的時(shí)間片資源只有和當(dāng)前線程同優(yōu)先級(jí)的線程才可以獲取到。
-
join
join方法的作用是父線程(一般是main主線程)等待子線程執(zhí)行完成后再執(zhí)行魏割,換言之就是講異步執(zhí)行的線程合并為同步的主線程,譬嚣。
同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("主線程開(kāi)始"+"時(shí)間:"+System.currentTimeMillis());
JoinDemo main = new JoinDemo();
Thread printer1 = new Thread(main.new MyPrinter("線程1"));
Thread printer2 = new Thread(main.new MyPrinter("線程2"));
Thread printer3 = new Thread(main.new MyPrinter("線程3"));
printer1.start();
printer1.join();
printer2.start();
printer2.join();
printer3.start();
System.out.println("主線程結(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)前線程"+content+"時(shí)間:"+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
輸出結(jié)果:
主線程開(kāi)始時(shí)間:1557824674063
當(dāng)前線程線程1時(shí)間:1557824674063
當(dāng)前線程線程2時(shí)間:1557824675065
主線程結(jié)束時(shí)間:1557824676065
當(dāng)前線程線程3時(shí)間:1557824676065
從上面例子可以看到線程1和2調(diào)用了join
方法后,主線程是等待兩個(gè)線程執(zhí)行完成之后才會(huì)繼續(xù)執(zhí)行
-
interrupt
interrupt的目的是為了中斷線程须揣,原來(lái)Thread.stop, Thread.suspend, Thread.resume 都有這個(gè)功能,但由于都太暴力了而被廢棄了钱豁,暴力中斷線程是一種不安全的操作耻卡,相對(duì)而言interrupt通過(guò)設(shè)置標(biāo)志位的方式就比較溫柔
interrupt
基于一個(gè)線程不應(yīng)該由其他線程來(lái)強(qiáng)制中斷或停止,而是應(yīng)該由線程內(nèi)部來(lái)自行停止的思想來(lái)實(shí)現(xiàn)的牲尺,自己的事自己處理卵酪,是一種比較溫柔和安全的做法,而且中斷不活動(dòng)的線程不會(huì)產(chǎn)生任何影響谤碳。
從API文檔的中的介紹來(lái)看interrupt()
的作用是中斷本線程溃卡。除非當(dāng)前線程正在中斷自身(始終允許),否則將調(diào)用此線程的checkAccess
方法蜒简,但這可能導(dǎo)致拋出SecurityException
瘸羡。
如果在調(diào)用Object類(lèi)的wait()
、join()
搓茬、sleep(long)
阻塞了這個(gè)線程犹赖,那么它的中斷狀態(tài)將被清除并收到InterruptedException
队他。
如果在InterruptibleChannel
上的I / O操作中阻塞了該線程,則該通道將被關(guān)閉峻村,線程的中斷狀態(tài)將被設(shè)置麸折,并且線程將收到ClosedByInterruptException
。
-
終止阻塞線程
例如粘昨,線程通過(guò)wait()進(jìn)入阻塞狀態(tài)垢啼,此時(shí)通過(guò)interrupt()
中斷該線程;調(diào)用interrupt()
會(huì)立即將線程的中斷標(biāo)記設(shè)為“true”张肾,但是由于線程處于阻塞狀態(tài)膊夹,所以該“中斷標(biāo)記”會(huì)立即被清除為“false”,同時(shí)捌浩,會(huì)產(chǎn)生一個(gè)InterruptedException
的異常放刨。此時(shí)將InterruptedException
放在適當(dāng)?shù)奈恢眠M(jì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),線程終止浪听!
}
}
但是如果需要將··InterruptedException··在··while(true)``循環(huán)體之內(nèi)的話螟碎,就需要額外的添加退出處理,通過(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)線程產(chǎn)生了InterruptedException異常時(shí)掉分,while(true)仍能繼續(xù)運(yùn)行!需要手動(dòng)退出
break;
}
}
}
-
終止運(yùn)行線程
通常克伊,我們通過(guò)“標(biāo)記”方式終止處于“運(yùn)行狀態(tài)”的線程酥郭。其中,包括“中斷標(biāo)記”和“額外添加標(biāo)記”愿吹。通過(guò)設(shè)立一個(gè)標(biāo)志來(lái)在線程運(yùn)行的時(shí)候判斷是否執(zhí)行下去不从。
@Override
public void run() {
while (!isInterrupted()) {
}
}
isInterrupted
是Thread
的內(nèi)部方法,可以獲取當(dāng)前線程是否中斷的標(biāo)志犁跪,當(dāng)線程處于運(yùn)行狀態(tài)時(shí)椿息,我們通過(guò)interrupt()
修改線程的中斷標(biāo)志,來(lái)達(dá)到退出while循環(huán)的作用坷衍。
上述是系統(tǒng)內(nèi)部的標(biāo)志符號(hào)寝优,我們也可以自己設(shè)置一個(gè)標(biāo)志符來(lái)達(dá)到退出線程的作用
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á)到退出線程的目的乏矾。
綜合阻塞和運(yùn)行狀態(tài)下線程的終止方式,結(jié)合兩者可以使用一個(gè)通用較為安全的方法
@Override
public void run() {
try {
// 1. isInterrupted()保證,只要中斷標(biāo)記為true就終止線程妻熊。
while (!isInterrupted()) {
}
} catch (InterruptedException ie) {
// 2. InterruptedException異常保證夸浅,當(dāng)InterruptedException異常產(chǎn)生時(shí),線程被終止扔役。
}
}
最后談?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婉刀,這算是多線程我們最常用的方法了
sleep是Thread的靜態(tài)native方法,它的作用是讓當(dāng)前線程按照指定的時(shí)間休眠序仙,休眠時(shí)期線程不會(huì)釋放鎖突颊,但是會(huì)讓出執(zhí)行當(dāng)前線程的cpu資源給其他線程使用,和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()
喚醒后還需要等待喚醒線程執(zhí)行完畢釋放鎖后,才會(huì)獲得CPU資源繼續(xù)執(zhí)行
-
線程的狀態(tài)轉(zhuǎn)換以及基本操作
Java 并發(fā)編程:線程間的協(xié)作
Java多線程系列--“基礎(chǔ)篇”09之 interrupt()和線程終止方式