1、操作系統(tǒng)中的線程狀態(tài)
操作系統(tǒng)中的線程狀態(tài)有運(yùn)行误趴、就緒、等待三個(gè)關(guān)鍵狀態(tài)
- 就緒狀態(tài)(ready):線程正在等待使用CPU,經(jīng)調(diào)度程序調(diào)用之后可進(jìn)入running狀態(tài)
- 執(zhí)行狀態(tài)(running):線程正在使用CPU
- 等待狀態(tài)(waiting): 線程經(jīng)過(guò)等待事件的調(diào)用或者正在等待其他資源(如I/O)
Q
操作系統(tǒng)中的線程為什么沒(méi)有掛起狀態(tài)屯烦?
A
由于線程不是資源的擁有單位,掛起狀態(tài)對(duì)線程是沒(méi)有意義的房铭,如果一個(gè)進(jìn)程掛起后被對(duì)換出主存驻龟,則它的所有線程因共享了進(jìn)程的地址空間,也必須全部對(duì)換出去缸匪∥毯可見(jiàn)由掛起操作引起的狀態(tài)是進(jìn)程級(jí)狀態(tài),不作為線程級(jí)狀態(tài)凌蔬。類(lèi)似地露懒,進(jìn)程的終止會(huì)導(dǎo)致進(jìn)程中所有線程的終止。
2砂心、Java中的六個(gè)線程狀態(tài)
Java中的線程狀態(tài)有六個(gè)線程狀態(tài)懈词。
Thread.State源碼
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW 尚未啟動(dòng)的線程處于此狀態(tài)。
RUNNABLE 在Java虛擬機(jī)中執(zhí)行的線程處于這種狀態(tài)辩诞。
BLOCKED 在等待監(jiān)視器鎖定的情況下被阻塞的線程處于此狀態(tài)坎弯。
WAITING 無(wú)限期地等待另一個(gè)線程執(zhí)行特定操作的線程處于此狀態(tài)。
TIMED_WAITING 正在等待另一個(gè)線程執(zhí)行操作的線程最多達(dá)到指定的等待時(shí)間,該線程處于此狀態(tài)抠忘。
TERMINATED 退出的線程處于此狀態(tài)撩炊。
在給定的時(shí)間點(diǎn),線程只能處于一種狀態(tài)崎脉。 這些狀態(tài)是虛擬機(jī)狀態(tài)衰抑,不反映任何操作系統(tǒng)線程狀態(tài)。
Q
同一個(gè)線程調(diào)用兩次start()方法是否可以荧嵌?
A
在調(diào)用一次start()之后呛踊,threadStatus的值會(huì)改變(threadStatus !=0),此時(shí)再次調(diào)用start()方法會(huì)拋出IllegalThreadStateException異常啦撮。
我們看一下start()方法的源碼
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
//這個(gè)是native方法
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
猜想谭网,new之后的threadStatus狀態(tài)為0,但是調(diào)用native start0()方法后赃春,狀態(tài)會(huì)變?yōu)槎嗌倌赜湓瘢课覀円黄餯ebug一下。
package co.dianjiu.thread;
public class MyThreadStatus extends Thread{
@Override
public void run(){
System.out.println("MyThreadStatus");
}
public static void main(String[] args) {
MyThreadStatus myThreadStatus = new MyThreadStatus();
System.out.println(myThreadStatus.getState());
myThreadStatus.start();
System.out.println(myThreadStatus.getState());
myThreadStatus.start();
System.out.println(myThreadStatus.getState());
}
}
NEW
MyThreadStatus
RUNNABLE
Exception in thread "main" java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:789)
at co.dianjiu.thread.MyThreadStatus.main(MyThreadStatus.java:14)
看下getState()方法的源碼
public State getState() {
// get current thread state
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
new 之后的 threadStatus斷點(diǎn)進(jìn)來(lái)的值為0
start 之后的 threadStatus斷點(diǎn)進(jìn)來(lái)的值為5
3织中、Java中的線程狀態(tài)轉(zhuǎn)換
3.1锥涕、阻塞狀態(tài)和就緒狀態(tài)的轉(zhuǎn)換
處于BLOCKED狀態(tài)的線程是因?yàn)樵诘却i的釋放。假如這里有兩個(gè)線程a和b狭吼,a線程提前獲得了鎖并且暫未釋放鎖层坠,此時(shí)b就處于BLOCKED狀態(tài)。我們先來(lái)看一個(gè)例子:
package co.dianjiu.thread;
public class MyThreadBlocked extends Thread{
@Override
public void run(){
testMethod();
}
public static void main(String[] args) throws InterruptedException {
MyThreadBlocked a = new MyThreadBlocked();
a.setName("a");
MyThreadBlocked b = new MyThreadBlocked();
b.setName("b");
a.start();
// 需要注意這里main線程休眠了1000毫秒刁笙,而testMethod()里休眠了2000毫秒
Thread.sleep(1000L);
b.start();
System.out.println(a.getName() + ":" + a.getState());
System.out.println(b.getName() + ":" + b.getState());
}
// 同步方法爭(zhēng)奪鎖
private static synchronized void testMethod() {
try {
Thread.sleep(2000L);
System.out.println(Thread.currentThread().getName()+"====>"+Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執(zhí)行結(jié)果
a:TIMED_WAITING
b:BLOCKED
a====>RUNNABLE
b====>RUNNABLE
3.2破花、等待狀態(tài)和就緒狀態(tài)的轉(zhuǎn)換
從上面的轉(zhuǎn)換圖可知,有三種方法可從等待狀態(tài)轉(zhuǎn)換到就緒狀態(tài)疲吸,讓我們一起看一下具體的使用案例及每個(gè)方法的源碼實(shí)現(xiàn)座每。
Object.wait()
調(diào)用wait()方法前線程必須持有對(duì)象的鎖。
線程調(diào)用wait()方法時(shí)摘悴,會(huì)釋放當(dāng)前的鎖峭梳,直到有其他線程調(diào)用notify()/notifyAll()方法喚醒等待鎖的線程。
package co.dianjiu.thread;
public class MyThreadWaiting extends Thread{
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> {
System.out.println("線程A等待獲取lock鎖");
synchronized (lock) {
try {
System.out.println("線程A獲取了lock鎖,將要運(yùn)行l(wèi)ock.wait()方法進(jìn)行等待");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
a.setName("a");
Thread b = new Thread(() -> {
System.out.println("線程B等待獲取lock鎖");
System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
synchronized (lock) {
System.out.println("線程B等待獲取lock鎖,將要運(yùn)行l(wèi)ock.notify()方法");
lock.notify();
//lock.notifyAll();
System.out.println(a.getName() + "===>" + Thread.currentThread().getState());
}
});
b.setName("b");
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
a.start();
System.out.println(a.getName() + "===>" + a.getState());
b.start();
System.out.println(a.getName() + "===>" + a.getState());
}
}
執(zhí)行結(jié)果
a===>NEW
b===>NEW
線程A等待獲取lock鎖
線程A獲取了lock鎖,將要運(yùn)行l(wèi)ock.wait()方法進(jìn)行等待
a===>RUNNABLE
線程B等待獲取lock鎖
a===>WAITING
b===>RUNNABLE
線程B等待獲取lock鎖,將要運(yùn)行l(wèi)ock.notify()方法
a===>RUNNABLE
看下wait方法的源碼蹂喻,其實(shí)wait()方法就等于wait(Long)方法值為0
public final void wait() throws InterruptedException {
wait(0L);
}
而喚醒方法調(diào)用的是Java本地接口(JNI)
其他線程調(diào)用notify()方法只會(huì)喚醒單個(gè)等待鎖的線程葱椭,如有有多個(gè)線程都在等待這個(gè)鎖的話不一定會(huì)喚醒到之前調(diào)用wait()方法的線程。同樣叉橱,調(diào)用notifyAll()方法喚醒所有等待鎖的線程之后挫以,也不一定會(huì)馬上把時(shí)間片分給剛才放棄鎖的那個(gè)線程,具體要看系統(tǒng)的調(diào)度窃祝。
@HotSpotIntrinsicCandidate
public final native void notify();
@HotSpotIntrinsicCandidate
public final native void notifyAll();
Thread.join()
調(diào)用join()方法不會(huì)釋放鎖掐松,會(huì)一直等待當(dāng)前線程執(zhí)行完畢(轉(zhuǎn)換為T(mén)ERMINATED狀態(tài))。
package co.dianjiu.thread;
public class MyThreadJoin extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
}
public static void main(String[] args) throws InterruptedException {
MyThreadBlocked a = new MyThreadBlocked();
a.setName("a");
MyThreadBlocked b = new MyThreadBlocked();
b.setName("b");
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
a.start();
a.join();
b.start();
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
}
}
執(zhí)行結(jié)果
a===>NEW
b===>NEW
a====>RUNNABLE
a===>TERMINATED
b===>TIMED_WAITING
b====>RUNNABLE
看下join方法的源碼,其實(shí)join()方法就等于join(Long)方法值為0
public final void join() throws InterruptedException {
join(0);
}
LockSupport.park()
在線程調(diào)度的時(shí)候禁用當(dāng)前線程大磺,將其放置到WaitSet隊(duì)列抡句。除非有許可證。所謂的許可證就是前面說(shuō)的0-1的標(biāo)識(shí)杠愧。使用unpark則就會(huì)產(chǎn)生一個(gè)許可待榔。
如果許可證可用,那么它將被消耗流济,并且調(diào)用將立即返回锐锣;否則,出于線程調(diào)度目的绳瘟,當(dāng)前線程將被禁用雕憔,并處于休眠狀態(tài),直到發(fā)生以下三種情況之一:
- 其他線程以當(dāng)前線程為目標(biāo)調(diào)用unpark方法糖声。
- 其他線程打斷當(dāng)前線程斤彼。
- 調(diào)用了虛假的返回。
package co.dianjiu.thread;
import java.util.concurrent.locks.LockSupport;
public class MyThreadPark {
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() -> {
System.out.println("線程A獲取了lock鎖,將要運(yùn)行LockSupport.park()方法進(jìn)行等待");
LockSupport.park(Thread.currentThread());
});
a.setName("a");
Thread b = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
System.out.println("線程B等待獲取lock鎖,將要運(yùn)行LockSupport.unpark()方法");
LockSupport.unpark(a);
//a.interrupt();
System.out.println(a.getName() + "===>" + Thread.currentThread().getState());
});
b.setName("b");
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
a.start();
System.out.println(a.getName() + "===>" + a.getState());
b.start();
System.out.println(a.getName() + "===>" + a.getState());
}
}
a===>NEW
b===>NEW
線程A獲取了lock鎖,將要運(yùn)行LockSupport.park()方法進(jìn)行等待
a===>RUNNABLE
a===>WAITING
b===>RUNNABLE
線程B等待獲取lock鎖,將要運(yùn)行LockSupport.unpark()方法
a===>RUNNABLE
3.3蘸泻、超時(shí)等待和就緒狀態(tài)的轉(zhuǎn)換
Thread.sleep(long)
使當(dāng)前線程睡眠指定時(shí)間琉苇。需要注意這里的“睡眠”只是暫時(shí)使線程停止執(zhí)行,并不會(huì)釋放鎖悦施。時(shí)間到后并扇,線程會(huì)重新進(jìn)入RUNNABLE狀態(tài)。
Object.wait(long)
wait(long)方法使線程進(jìn)入TIMED_WAITING狀態(tài)歼争。這里的wait(long)方法與無(wú)參方法wait()相同的地方是拜马,都可以通過(guò)其他線程調(diào)用notify()或notifyAll()方法來(lái)喚醒渗勘。
不同的地方是沐绒,有參方法wait(long)就算其他線程不來(lái)喚醒它,經(jīng)過(guò)指定時(shí)間long之后它會(huì)自動(dòng)喚醒旺坠,擁有去爭(zhēng)奪鎖的資格乔遮。
Thread.join(long)
join(long)使當(dāng)前線程執(zhí)行指定時(shí)間,并且使線程進(jìn)入TIMED_WAITING狀態(tài)取刃。
我們?cè)賮?lái)改一改剛才的示例:
package co.dianjiu.thread;
public class MyThreadJoinTime extends Thread{
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
}
public static void main(String[] args) throws InterruptedException {
MyThreadBlocked a = new MyThreadBlocked();
a.setName("a");
MyThreadBlocked b = new MyThreadBlocked();
b.setName("b");
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
a.start();
a.join(1000L);
b.start();
System.out.println(a.getName() + "===>" + a.getState());
System.out.println(b.getName() + "===>" + b.getState());
}
}
執(zhí)行結(jié)果
a===>NEW
b===>NEW
a===>TIMED_WAITING
b===>BLOCKED
a====>RUNNABLE
b====>RUNNABLE
因?yàn)槭侵付司唧wa線程執(zhí)行的時(shí)間的蹋肮,并且執(zhí)行時(shí)間是小于a線程sleep的時(shí)間,所以a線程狀態(tài)輸出TIMED_WAITING璧疗。b線程狀態(tài)仍然不固定(RUNNABLE或BLOCKED)坯辩。
3.4、線程中斷狀態(tài)
在某些情況下崩侠,我們?cè)诰€程啟動(dòng)后發(fā)現(xiàn)并不需要它繼續(xù)執(zhí)行下去時(shí)漆魔,需要中斷線程。目前在Java里還沒(méi)有安全直接的方法來(lái)停止線程,但是Java提供了線程中斷機(jī)制來(lái)處理需要中斷線程的情況改抡。
線程中斷機(jī)制是一種協(xié)作機(jī)制矢炼。需要注意,通過(guò)中斷操作并不能直接終止一個(gè)線程阿纤,而是通知需要被中斷的線程自行處理句灌。
簡(jiǎn)單介紹下Thread類(lèi)里提供的關(guān)于線程中斷的幾個(gè)方法:
- Thread.interrupt():中斷線程。這里的中斷線程并不會(huì)立即停止線程欠拾,而是設(shè)置線程的中斷狀態(tài)為true(默認(rèn)是flase)胰锌;
- Thread.interrupted():測(cè)試當(dāng)前線程是否被中斷。線程的中斷狀態(tài)受這個(gè)方法的影響藐窄,意思是調(diào)用一次使線程中斷狀態(tài)設(shè)置為true匕荸,連續(xù)調(diào)用兩次會(huì)使得這個(gè)線程的中斷狀態(tài)重新轉(zhuǎn)為false;
- Thread.isInterrupted():測(cè)試當(dāng)前線程是否被中斷枷邪。與上面方法不同的是調(diào)用這個(gè)方法并不會(huì)影響線程的中斷狀態(tài)榛搔。
在線程中斷機(jī)制里,當(dāng)其他線程通知需要被中斷的線程后东揣,線程中斷的狀態(tài)被設(shè)置為true践惑,但是具體被要求中斷的線程要怎么處理,完全由被中斷線程自己而定嘶卧,可以在合適的實(shí)際處理中斷請(qǐng)求尔觉,也可以完全不處理繼續(xù)執(zhí)行下去。