04_線程狀態(tài)轉(zhuǎn)換方法

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í)行下去。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末芥吟,一起剝皮案震驚了整個(gè)濱河市侦铜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钟鸵,老刑警劉巖钉稍,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異棺耍,居然都是意外死亡贡未,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)蒙袍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)俊卤,“玉大人,你說(shuō)我怎么就攤上這事害幅∠校” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵以现,是天一觀的道長(zhǎng)狠怨。 經(jīng)常有香客問(wèn)我佩抹,道長(zhǎng),這世上最難降的妖魔是什么取董? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任棍苹,我火速辦了婚禮,結(jié)果婚禮上茵汰,老公的妹妹穿的比我還像新娘枢里。我一直安慰自己,他們只是感情好蹂午,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布栏豺。 她就那樣靜靜地躺著,像睡著了一般豆胸。 火紅的嫁衣襯著肌膚如雪奥洼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天晚胡,我揣著相機(jī)與錄音灵奖,去河邊找鬼。 笑死估盘,一個(gè)胖子當(dāng)著我的面吹牛瓷患,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遣妥,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼擅编,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了箫踩?” 一聲冷哼從身側(cè)響起爱态,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎境钟,沒(méi)想到半個(gè)月后锦担,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吱韭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年吆豹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片理盆。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖凑阶,靈堂內(nèi)的尸體忽然破棺而出猿规,到底是詐尸還是另有隱情,我是刑警寧澤宙橱,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布姨俩,位于F島的核電站蘸拔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏环葵。R本人自食惡果不足惜调窍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望张遭。 院中可真熱鬧邓萨,春花似錦、人聲如沸菊卷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)洁闰。三九已至歉甚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扑眉,已是汗流浹背纸泄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腰素,地道東北人刃滓。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像耸弄,于是被迫代替她去往敵國(guó)和親咧虎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容