溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

什么是Java中Thread構(gòu)造方法

發(fā)布時(shí)間:2021-10-11 21:56:49 來源:億速云 閱讀:136 作者:iii 欄目:編程語(yǔ)言

這篇文章主要講解了“什么是Java中Thread構(gòu)造方法”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“什么是Java中Thread構(gòu)造方法”吧!

線程生命周期

五個(gè)階段

線程生命周期可以分為五個(gè)階段:

  • NEW

  • RUNNABLE

  • RUNNING

  • BLOCKED

  • TERMINATED

NEW

new創(chuàng)建一個(gè)Thread對(duì)象時(shí),但是并沒有使用start()啟動(dòng)線程,此時(shí)線程處于NEW狀態(tài)。準(zhǔn)確地說,只是Thread對(duì)象的狀態(tài),這就是一個(gè)普通的Java對(duì)象。此時(shí)可以通過start()方法進(jìn)入RUNNABLE狀態(tài)。

RUNNABLE

進(jìn)入RUNNABLE狀態(tài)必須調(diào)用start()方法,這樣就在JVM中創(chuàng)建了一個(gè)線程。但是,線程一經(jīng)創(chuàng)建,并不能馬上被執(zhí)行,線程執(zhí)行與否需要聽令于CPU調(diào)度,也就是說,此時(shí)是處于可執(zhí)行狀態(tài),具備執(zhí)行的資格,但是并沒有真正執(zhí)行起來,而是在等待被調(diào)度。

RUNNABLE狀態(tài)只能意外終止或進(jìn)入RUNNING狀態(tài)。

RUNNING

一旦CPU通過輪詢或其他方式從任務(wù)可執(zhí)行隊(duì)列中選中了線程,此時(shí)線程才能被執(zhí)行,也就是處于RUNNING狀態(tài),在該狀態(tài)中,可能發(fā)生的狀態(tài)轉(zhuǎn)換如下:

  • 進(jìn)入TERMINATED:比如調(diào)用已經(jīng)不推薦的stop()方法

  • 進(jìn)入BLOCKED:比如調(diào)用了sleep()/wait()方法,或者進(jìn)行某個(gè)阻塞操作(獲取鎖資源、磁盤IO等)

  • 進(jìn)入RUNNABLECPU時(shí)間片到,或者線程主動(dòng)調(diào)用yield()

BLOCKED

也就是阻塞狀態(tài),進(jìn)入阻塞狀態(tài)的原因很多,常見的如下:

  • 磁盤IO

  • 網(wǎng)絡(luò)操作

  • 為了獲取鎖而進(jìn)入阻塞操作

處于BLOCKED狀態(tài)時(shí),可能發(fā)生的狀態(tài)轉(zhuǎn)換如下:

  • 進(jìn)入TERMINATED:比如調(diào)用不推薦的stop(),或者JVM意外死亡

  • 進(jìn)入RUNNABLE:比如休眠結(jié)束、被notify()/nofityAll()喚醒、獲取到某個(gè)鎖、阻塞過程被interrupt()打斷等

TERMINATED

TERMINATED是線程的最終狀態(tài),進(jìn)入該狀態(tài)后,意味著線程的生命周期結(jié)束,比如在下列情況下會(huì)進(jìn)入該狀態(tài):

  • 線程運(yùn)行正常結(jié)束

  • 線程運(yùn)行出錯(cuò)意外結(jié)束

  • JVM意外崩潰,導(dǎo)致所有線程都強(qiáng)制結(jié)束

Thread構(gòu)造方法

構(gòu)造方法

Thread的構(gòu)造方法一共有八個(gè),這里根據(jù)命名方式分類,使用默認(rèn)命名的構(gòu)造方法如下:

  • Thread()

  • Thread(Runnable target)

  • Thread(ThreadGroup group,Runnable target)

命名線程的構(gòu)造方法如下:

  • Thread(String name)

  • Thread(Runnable target,Strintg name)

  • Thread(ThreadGroup group,String name)

  • Thread(ThreadGroup group,Runnable target,String name)

  • Thread(ThreadGroup group,Runnable target,String name,long stackSize)

但實(shí)際上所有的構(gòu)造方法最終都是調(diào)用如下私有構(gòu)造方法:

private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);

在默認(rèn)命名構(gòu)造方法中,在源碼中可以看到,默認(rèn)命名其實(shí)就是Thread-X的命令(X為數(shù)字):

public Thread() {
    this((ThreadGroup)null, (Runnable)null, "Thread-" + nextThreadNum(), 0L);
}

public Thread(Runnable target) {
    this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L);
}

private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

而在命名構(gòu)造方法就是自定義的名字。

另外,如果想修改線程的名字,可以調(diào)用setName()方法,但是需要注意,處于NEW狀態(tài)的線程才能修改。

線程的父子關(guān)系

Thread的所有構(gòu)造方法都會(huì)調(diào)用如下方法:

private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals);

其中的一段源碼截取如下:

if (name == null) {
    throw new NullPointerException("name cannot be null");
} else {
    this.name = name;
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }

        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
}

可以看到當(dāng)前這里有一個(gè)局部變量叫parent,并且賦值為currentThread(),currentThread()是一個(gè)native方法。因?yàn)橐粋€(gè)線程被創(chuàng)建時(shí)的最初狀態(tài)為NEW,因此currentThread()代表是創(chuàng)建自身線程的那個(gè)線程,也就是說,結(jié)論如下:

  • 一個(gè)線程的創(chuàng)建肯定是由另一個(gè)線程完成的

  • 被創(chuàng)建線程的父線程是創(chuàng)建它的線程

也就是自己創(chuàng)建的線程,父線程為main線程,而main線程由JVM創(chuàng)建。

另外,Thread的構(gòu)造方法中有幾個(gè)具有ThreadGroup參數(shù),該參數(shù)指定了線程位于哪一個(gè)ThreadGroup,如果一個(gè)線程創(chuàng)建的時(shí)候沒有指定ThreadGroup,那么將會(huì)和父線程同一個(gè)ThreadGroupmain線程所在的ThreadGroup稱為main。

關(guān)于stackSize

Thread構(gòu)造方法中有一個(gè)stackSize參數(shù),該參數(shù)指定了JVM分配線程棧的地址空間的字節(jié)數(shù),對(duì)平臺(tái)依賴性較高,在一些平臺(tái)上:

  • 設(shè)置較大的值:可以使得線程內(nèi)調(diào)用遞歸深度增加,降低StackOverflowError出現(xiàn)的概率

  • 設(shè)置較低的值:可以使得創(chuàng)建的線程數(shù)增多,可以推遲OutOfMemoryError出現(xiàn)的時(shí)間

但是,在一些平臺(tái)上該參數(shù)不會(huì)起任何作用。另外,如果設(shè)置為0也不會(huì)起到任何作用。

Thread API

sleep()

sleep()有兩個(gè)重載方法:

  • sleep(long mills)

  • sleep(long mills,int nanos)

但是在JDK1.5后,引入了TimeUnit,其中對(duì)sleep()方法提供了很好的封裝,建議使用TimeUnit.XXXX.sleep()去代替Thread.sleep()

TimeUnit.SECONDS.sleep(1);
TimeUnit.MINUTES.sleep(3);

yield()

yield()屬于一種啟發(fā)式方法,提醒CPU調(diào)度器當(dāng)前線程會(huì)自愿放棄資源,如果CPU資源不緊張,會(huì)忽略這種提醒。調(diào)用yield()方法會(huì)使當(dāng)前線程從RUNNING變?yōu)?code>RUNNABLE狀態(tài)。

關(guān)于yield()sleep()的區(qū)別,區(qū)別如下:

  • sleep()會(huì)導(dǎo)致當(dāng)前線程暫停指定的時(shí)間,沒有CPU時(shí)間片的消耗

  • yield()只是對(duì)CPU調(diào)度器的一個(gè)提示,如果CPU調(diào)度器沒有忽略這個(gè)提示,會(huì)導(dǎo)致線程上下文的切換

  • sleep()會(huì)使線程短暫阻塞,在給定時(shí)間內(nèi)釋放CPU資源

  • 如果yield()生效,yield()會(huì)使得從RUNNING狀態(tài)進(jìn)入RUNNABLE狀態(tài)

  • sleep()會(huì)幾乎百分百地完成給定時(shí)間的休眠,但是yield()的提示不一定能擔(dān)保

  • 一個(gè)線程調(diào)用sleep()而另一個(gè)線程調(diào)用interrupt()會(huì)捕獲到中斷信號(hào),而yield則不會(huì)

setPriority()

優(yōu)先級(jí)介紹

線程與進(jìn)程類似,也有自己的優(yōu)先級(jí),理論上來說,優(yōu)先級(jí)越高的線程會(huì)有優(yōu)先被調(diào)度的機(jī)會(huì),但實(shí)際上并不是如此,設(shè)置優(yōu)先級(jí)與yield()類似,也是一個(gè)提醒性質(zhì)的操作:

  • 對(duì)于root用戶,會(huì)提醒操作系統(tǒng)想要設(shè)置的優(yōu)先級(jí)別,否則會(huì)被忽略

  • 如果CPU比較忙,設(shè)置優(yōu)先級(jí)可能會(huì)獲得更多的CPU時(shí)間片,但是空閑時(shí)優(yōu)先級(jí)的高低幾乎不會(huì)有任何作用

所以,設(shè)置優(yōu)先級(jí)只是很大程度上讓某個(gè)線程盡可能獲得比較多的執(zhí)行機(jī)會(huì),也就是讓線程自己盡可能被操作系統(tǒng)調(diào)度,而不是設(shè)置了高優(yōu)先級(jí)就一定優(yōu)先運(yùn)行,或者說優(yōu)先級(jí)高的線程比優(yōu)先級(jí)低的線程就一定優(yōu)先運(yùn)行。

優(yōu)先級(jí)源碼分析

設(shè)置優(yōu)先級(jí)直接調(diào)用setPriority()即可,OpenJDK 11源碼如下:

public final void setPriority(int newPriority) {
    this.checkAccess();
    if (newPriority <= 10 && newPriority >= 1) {
        ThreadGroup g;
        if ((g = this.getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }

            this.setPriority0(this.priority = newPriority);
        }

    } else {
        throw new IllegalArgumentException();
    }
}

可以看到優(yōu)先級(jí)處于[1,10]之間,而且不能設(shè)置為大于當(dāng)前ThreadGroup的優(yōu)先級(jí),最后通過native方法setPriority0設(shè)置優(yōu)先級(jí)。

一般情況下,不會(huì)對(duì)線程的優(yōu)先級(jí)設(shè)置級(jí)別,默認(rèn)情況下,線程的優(yōu)先級(jí)為5,因?yàn)?code>main線程的優(yōu)先級(jí)為5,而且main為所有線程的父進(jìn)程,因此默認(rèn)情況下線程的優(yōu)先級(jí)也是5。

interrupt()

interrupt()是一個(gè)重要的API,線程中斷的API有如下三個(gè):

  • void interrupt()

  • boolean isInterrupted()

  • static boolean interrupted()

下面對(duì)其逐一進(jìn)行分析。

interrupt()

一些方法調(diào)用會(huì)使得當(dāng)前線程進(jìn)入阻塞狀態(tài),比如:

  • Object.wait()

  • Thread.sleep()

  • Thread.join()

  • Selector.wakeup()

而調(diào)用interrupt()可以打斷阻塞,打斷阻塞并不等于線程的生命周期結(jié)束,僅僅是打斷了當(dāng)前線程的阻塞狀態(tài)。一旦在阻塞狀態(tài)下被打斷,就會(huì)拋出一個(gè)InterruptedException的異常,這個(gè)異常就像一個(gè)信號(hào)一樣通知當(dāng)前線程被打斷了,例子如下:

public static void main(String[] args) throws InterruptedException{
    Thread thread = new Thread(()->{
        try{
            TimeUnit.SECONDS.sleep(10);
        }catch (InterruptedException e){
            System.out.println("Thread is interrupted.");
        }
    });
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    thread.interrupt();
}

會(huì)輸出線程被中斷的信息。

isInterrupted()

isInterrupted()可以判斷當(dāng)前線程是否被中斷,僅僅是對(duì)interrupt()標(biāo)識(shí)的一個(gè)判斷,并不會(huì)影響標(biāo)識(shí)發(fā)生任何改變(因?yàn)檎{(diào)用interrupt()的時(shí)候會(huì)設(shè)置內(nèi)部的一個(gè)叫interrupt flag的標(biāo)識(shí)),例子如下:

public static void main(String[] args) throws InterruptedException{
    Thread thread = new Thread(()->{
        while (true){}
    });
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Thread is interrupted :"+thread.isInterrupted());
    thread.interrupt();
    System.out.println("Thread is interrupted :"+thread.isInterrupted());
}

輸出結(jié)果為:

Thread is interrupted :false
Thread is interrupted :true

另一個(gè)例子如下:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    System.out.println("In catch block thread is interrupted :" + isInterrupted());
                }
            }
        }
    };
    thread.start();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Thread is interrupted :" + thread.isInterrupted());
    thread.interrupt();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Thread is interrupted :" + thread.isInterrupted());
}

輸出結(jié)果:

Thread is interrupted :false
In catch block thread is interrupted :false
Thread is interrupted :false

一開始線程未被中斷,結(jié)果為false,調(diào)用中斷方法后,在循環(huán)體內(nèi)捕獲到了異常(信號(hào)),此時(shí)會(huì)Thread自身會(huì)擦除interrupt標(biāo)識(shí),將標(biāo)識(shí)復(fù)位,因此捕獲到異常后輸出結(jié)果也為false。

interrupted()

這是一個(gè)靜態(tài)方法,調(diào)用該方法會(huì)擦除掉線程的interrupt標(biāo)識(shí),需要注意的是如果當(dāng)前線程被打斷了:

  • 第一次調(diào)用interrupted()會(huì)返回true,并且立即擦除掉interrupt標(biāo)識(shí)

  • 第二次包括以后的調(diào)用永遠(yuǎn)都會(huì)返回false,除非在此期間線程又一次被打斷

例子如下:

public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (true) {
                System.out.println(Thread.interrupted());
            }
        }
    };
    thread.setDaemon(true);
    thread.start();
    TimeUnit.MILLISECONDS.sleep(2);
    thread.interrupt();
}

輸出(截取一部分):

false
false
false
true
false
false
false

可以看到其中帶有一個(gè)true,也就是interrupted()判斷到了其被中斷,此時(shí)會(huì)立即擦除中斷標(biāo)識(shí),并且只有該次返回true,后面都是false。

關(guān)于interrupted()isInterrupted()的區(qū)別,可以從源碼(OpenJDK 11)知道:

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

public boolean isInterrupted() {
    return this.isInterrupted(false);
}

@HotSpotIntrinsicCandidate
private native boolean isInterrupted(boolean var1);

實(shí)際上兩者都是調(diào)用同一個(gè)native方法,其中的布爾變量表示是否擦除線程的interrupt標(biāo)識(shí):

  • true表示想要擦除,interrupted()就是這樣做的

  • false表示不想擦除,isInterrupted()就是這樣做的

join()

join()簡(jiǎn)介

join()sleep()一樣,都是屬于可以中斷的方法,如果其他線程執(zhí)行了對(duì)當(dāng)前線程的interrupt操作,也會(huì)捕獲到中斷信號(hào),并且擦除線程的interrupt標(biāo)識(shí),join()提供了三個(gè)API,分別如下:

  • void join()

  • void join(long millis,int nanos)

  • void join(long mills)

 例子

一個(gè)簡(jiǎn)單的例子如下:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = IntStream.range(1,3).mapToObj(Main::create).collect(Collectors.toList());
        threads.forEach(Thread::start);
        for (Thread thread:threads){
            thread.join();
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+" # "+i);
            shortSleep();
        }
    }

    private static Thread create(int seq){
        return new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+" # "+i);
                shortSleep();
            }
        },String.valueOf(seq));
    }

    private static void shortSleep(){
        try{
            TimeUnit.MILLISECONDS.sleep(2);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

輸出截取如下:

2 # 8
1 # 8
2 # 9
1 # 9
main # 0
main # 1
main # 2
main # 3
main # 4

線程1和線程2交替執(zhí)行,而main線程會(huì)等到線程1和線程2執(zhí)行完畢后再執(zhí)行。

線程關(guān)閉

Thread中有一個(gè)過時(shí)的方法stop,可以用于關(guān)閉線程,但是存在的問題是有可能不會(huì)釋放monitor的鎖,因此不建議使用該方法關(guān)閉線程。線程的關(guān)閉可以分為三類:

  • 正常關(guān)閉

  • 異常退出

  • 假死

正常關(guān)閉

正常結(jié)束

線程運(yùn)行結(jié)束后,就會(huì)正常退出,這是最普通的一種情況。

捕獲信號(hào)關(guān)閉線程

通過捕獲中斷信號(hào)去關(guān)閉線程,例子如下:

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
        @Override
        public void run() {
            System.out.println("work...");
            while(!isInterrupted()){

            }
            System.out.println("exit...");
        }
    };
    t.start();
    TimeUnit.SECONDS.sleep(5);
    System.out.println("System will be shutdown.");
    t.interrupt();
}

一直檢查interrupt標(biāo)識(shí)是否設(shè)置為true,設(shè)置為true則跳出循環(huán)。另一種方式是使用sleep()

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(){
        @Override
        public void run() {
            System.out.println("work...");
            while(true){
                try{
                    TimeUnit.MILLISECONDS.sleep(1);
                }catch (InterruptedException e){
                    break;
                }
            }
            System.out.println("exit...");
        }
    };
    t.start();
    TimeUnit.SECONDS.sleep(5);
    System.out.println("System will be shutdown.");
    t.interrupt();
}

volatile

由于interrupt標(biāo)識(shí)很有可能被擦除,或者不會(huì)調(diào)用interrupt()方法,因此另一種方法是使用volatile修飾一個(gè)布爾變量,并不斷循環(huán)判斷:

public class Main {
    static class MyTask extends Thread{
        private volatile boolean closed = false;

        @Override
        public void run() {
            System.out.println("work...");
            while (!closed && !isInterrupted()){

            }
            System.out.println("exit...");
        }

        public void close(){
            this.closed = true;
            this.interrupt();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyTask t = new MyTask();
        t.start();
        TimeUnit.SECONDS.sleep(5);
        System.out.println("System will be shutdown.");
        t.close();
    }
}

異常退出

線程執(zhí)行單元中是不允許拋出checked異常的,如果在線程運(yùn)行過程中需要捕獲checked異常并且判斷是否還有運(yùn)行下去的必要,可以將checked異常封裝為unchecked異常,比如RuntimeException,拋出從而結(jié)束線程的生命周期。

假死

所謂假死就是雖然線程存在,但是卻沒有任何的外在表現(xiàn),比如:

  • 沒有日志輸出

  • 不進(jìn)行任何的作業(yè)

等等,雖然此時(shí)線程是存在的,但看起來跟死了一樣,事實(shí)上是沒有死的,出現(xiàn)這種情況,很大可能是因?yàn)榫€程出現(xiàn)了阻塞,或者兩個(gè)線程爭(zhēng)奪資源出現(xiàn)了死鎖。

這種情況需要借助一些外部工具去判斷,比如VisualVM、jconsole等等,找出存在問題的線程以及當(dāng)前的狀態(tài),并判斷是哪個(gè)方法造成了阻塞。

感謝各位的閱讀,以上就是“什么是Java中Thread構(gòu)造方法”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)什么是Java中Thread構(gòu)造方法這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI