【Java并發(fā)001】使用級別:線程相關(guān)知識全解析

一诉位、前言

本文介紹Java線程相關(guān)知識(不包括線程同步+線程通信骑脱,這個內(nèi)容在筆者的另一篇博客中介紹過了),包括:線程生命周期苍糠、線程優(yōu)先級叁丧、線程禮讓、后臺線程岳瞭、聯(lián)合線程拥娄。

二、線程生命周期

2.1 引子:線程生命周期

本節(jié)闡述線程生命周期相關(guān)知識瞳筏,Java支持多線程技術(shù)稚瘾,除了Main函數(shù)主導(dǎo)一個main線程以外,可以用代碼創(chuàng)建一系列的前臺線程姚炕、后臺線程(本文后面會講)摊欠,每一個線程都有自己的生命周期,線程生命周期的不同狀態(tài)有不同的說法:

(1)有的說Java線程5種狀態(tài)柱宦,這是因為將“等待狀態(tài)Waiting+限時等待狀態(tài)Timed_Waiting”作為一種狀態(tài)些椒,5種狀態(tài)為:
新建狀態(tài)New、可運行狀態(tài)Runnable(Running+Ready)掸刊、等待狀態(tài)Waiting+Timed_Waiting免糕、阻塞狀態(tài)Blocked、結(jié)束狀態(tài)Terminated

(2)有的說Java線程6種狀態(tài),這是因為將將Running和Ready兩種狀態(tài)拆分開了石窑,6種狀態(tài)為:
新建狀態(tài)New牌芋、準(zhǔn)備狀態(tài)Ready、運行狀態(tài)Running尼斧、等待狀態(tài)Waiting+Timed_Waiting姜贡、阻塞狀態(tài)Blocked试吁、結(jié)束狀態(tài)Terminated

或者將等待狀態(tài)Waiting和限時等待狀態(tài)Timed_Waiting兩種狀態(tài)拆開棺棵,6種狀態(tài)為:
新建狀態(tài)New、可運行狀態(tài)Runnable(Running+Ready)熄捍、等待狀態(tài)Waiting烛恤、計時等待狀態(tài)Timed_Waiting、阻塞狀態(tài)Blocked余耽、結(jié)束狀態(tài)Terminated

(3)有的說Java線程7種狀態(tài)缚柏,這是因為將將Running和Ready兩種狀態(tài)拆分開、等待狀態(tài)Waiting和限時等待狀態(tài)Timed_Waiting兩種狀態(tài)拆開碟贾,7種狀態(tài)為:
新建狀態(tài)New币喧、準(zhǔn)備狀態(tài)Ready、運行狀態(tài)Running袱耽、 等待狀態(tài)Waiting杀餐、計時等待狀態(tài)Timed_Waiting、阻塞狀態(tài)Blocked朱巨、結(jié)束狀態(tài)Terminated

不管采用哪種說法史翘,Java線程狀態(tài)以下幾種,新建狀態(tài)New冀续、可運行狀態(tài)Runnable(Running+Ready)琼讽、等待狀態(tài)(等待狀態(tài)Waiting+限時等待狀態(tài)Timed_Waiting)、阻塞狀態(tài)Blocked洪唐、結(jié)束狀態(tài)Terminated

本文采用6種狀態(tài)的說法钻蹬,下面2.3 會具體闡述。

2.2 用一段代碼來演示一個完整的生命周期

代碼1:

package mypackage;
//  線程的生命周期凭需,演示線程的六種狀態(tài): NEW 新建狀態(tài)   RUNNABLE 可運行狀態(tài)(ready就緒狀態(tài)+running運行狀態(tài)) TIMED_WAITING 計時等待狀態(tài)
//   WAITING 等待狀態(tài)  BLOCKED 阻塞狀態(tài)  TERMINATED  終止?fàn)顟B(tài)(進(jìn)入終止?fàn)顟B(tài)后只能結(jié)束问欠,無法再返回回來)
class Block {   //共享資源類
    public boolean waitStatus = true;
    public void waitOp() throws InterruptedException {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + "  進(jìn)入wait");
            wait();
            // waitThread被notifyThread喚醒
            System.out.println(Thread.currentThread().getName() + "  next to Block.wait(), in the loop");
        }
    }
    public void notifyOp() {
        synchronized (this) {
            this.waitStatus = false; // 修改標(biāo)志位 等待狀態(tài)waitStatus=false
            notifyAll(); // 喚醒WaitRunnable
            System.out.println(Thread.currentThread().getName() + "  向正在wait當(dāng)前對象的線程發(fā)出notify");
        }
    }
}
class WaitThread implements Runnable {
    private Block block;
    public WaitThread(Block block) {
        super();
        this.block = block;
    }
    @Override
    public void run() {  
        //該方法中測試了 sleep(毫秒數(shù))進(jìn)入計時等待狀態(tài)和wait()進(jìn)入等待狀態(tài)
        // 前者只能等待2秒后自動喚醒   后者由notifyThread使用notify()/notifyAll()喚醒
        try {
            System.out.println(Thread.currentThread().getName() + "  被啟動,開始執(zhí)行...");
            // Thread.sleep(毫秒數(shù)) 進(jìn)入計時等待狀態(tài) 線程計時等待狀態(tài)中功炮,線程依然存活溅潜,線程isAlive() 仍然為true
            System.out.println(Thread.currentThread().getName() + "  開始sleep 2s");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "  從sleep中醒來");
            // 從sleep中醒來后,進(jìn)入wait狀態(tài)
            while (block.waitStatus) {
                block.waitOp(); // 進(jìn)入等待狀態(tài)薪伏,線程在等待狀態(tài)中滚澜,線程依然存活,線程isAlive()
                                // 仍然為true嫁怀,等待狀態(tài)只有一個出口设捐,notify()/notifyAll()
                // waitThread被notifyThread喚醒
                System.out.println(Thread.currentThread().getName() + "  next to WaitRunnable.waitOp(), in the loop");
            }
            System.out.println(Thread.currentThread().getName() + "  out of wait loop");
            System.out.println(Thread.currentThread().getName() + "  任務(wù)執(zhí)行結(jié)束借浊,即將終止...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class NotifyThread implements Runnable {
    private Block block;
    public NotifyThread(Block block) {
        super();
        this.block = block;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "  被啟動,開始執(zhí)行...");
        block.notifyOp();
        synchronized (block) {
            try {
                System.out.println(Thread.currentThread().getName() + "  開始sleep 6s萝招,并且不會釋放當(dāng)前對象的鎖");
                // 進(jìn)入計時等待狀態(tài)有兩種方式 wait(毫秒數(shù)) sleep(毫秒數(shù)) 兩者不同在于
                // wait(毫秒數(shù))釋放鎖 ,其他線程(本程序中另一個線程)獲得鎖并運行
                // sleep(毫秒數(shù))不釋放鎖,其他線程(本程序中另一個線程)不可以獲得鎖并運行
                // 現(xiàn)在notifyThread sleep(6000) 不釋放鎖蚂斤,所以waitThread也不能運行
                // ,只能等到6秒后notifyThread醒來
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // notifyThread醒來了,打印一下
        System.out.println(Thread.currentThread().getName() + "  從sleep中醒來");
    }
}
public class Test {
    private static final String IN_THE_STATE = "當(dāng)前的狀態(tài)為:";
    private static final String ISALIVE_RETRUN = "isAlive()返回為:";
    public static void printStatus(Thread thread) {
        System.out.println(thread.getName() + "  " + IN_THE_STATE + thread.getState().toString() + "  " + ISALIVE_RETRUN
                + thread.isAlive());
    }
    public static void main(String[] args) throws InterruptedException {
        // 該對象將作為后面加鎖的對象
        Block block = new Block();
        // new Thread() 進(jìn)入新建狀態(tài)
        Thread waitThread = new Thread(new WaitThread(block));
        Thread notifyThread = new Thread(new NotifyThread(block));
        printStatus(waitThread); // 打印當(dāng)前狀態(tài)槐沼,當(dāng)前狀態(tài)為新建狀態(tài) 線程尚未啟動曙蒸,isAlive()為false
        printStatus(notifyThread);// 打印當(dāng)前狀態(tài),當(dāng)前狀態(tài)為新建狀態(tài) 線程尚未啟動岗钩,isAlive()為false
        // .start() 進(jìn)入可運行狀態(tài) (包括就緒狀態(tài)和運行狀態(tài))
        // 就緒狀態(tài)進(jìn)入運行狀態(tài)是CPU調(diào)度纽窟,程序員無法控制,所本程序中對這兩種狀態(tài)不做區(qū)分兼吓,統(tǒng)一為可運行狀態(tài)
        waitThread.start();
        printStatus(waitThread); // 打印當(dāng)前狀態(tài)臂港,當(dāng)前狀態(tài)為可運行狀態(tài) 線程啟動,isAlive()為true
        Thread.sleep(1000);
        // TIMED_WAITING
        printStatus(waitThread); // 打印當(dāng)前狀態(tài)视搏,當(dāng)前狀態(tài)為計時等待狀態(tài)
        Thread.sleep(2000);
        // WAITING
        printStatus(waitThread); // 打印當(dāng)前狀態(tài)审孽,當(dāng)前狀態(tài)為wait()等待狀態(tài),等待被notifyThread()喚醒才能繼續(xù)下去
        Thread.sleep(1000); // 主線程main線程等待1s
        notifyThread.start(); // 啟動notify線程,去喚醒處于等待狀態(tài)的waitThread
        printStatus(notifyThread); // 打印notifyThread當(dāng)前狀態(tài)浑娜,當(dāng)前狀態(tài)為可運行狀態(tài)
                                    // 線程啟動佑力,isAlive()為true
        Thread.sleep(1000); // 主線程main線程等待1s
        // BLOCKED or TERMINATED
        // TERMINATED:運行到此處時,如果notifyThread執(zhí)行完notify操作后棚愤,調(diào)度器立馬切換至thread的情況下搓萧,thread會先行終止,調(diào)度器再調(diào)度notifyThread
        printStatus(waitThread); // 現(xiàn)在處于notifyThread sleep(6000) 睡眠6秒過程中  我們打印一下被notifyThread喚醒的waitThread處于什么狀態(tài)
                                    // waitThread為Block狀態(tài),islive為true
        //這是因為waitThread雖然被notifyThread喚醒宛畦,從等待狀態(tài)中出來瘸洛,但是因為notifyThread現(xiàn)在一直占用同步鎖,waitThread無法獲得同步鎖次和,所以處于阻塞狀態(tài)
        //等待狀態(tài)和阻塞狀態(tài)區(qū)別:Java中反肋,每個對象都有兩個池,鎖池和等待池踏施,
        //阻塞狀態(tài)兩種:I/O阻塞(本程序不涉及石蔗,略)和獲取同步鎖失敗而阻塞(本程序當(dāng)前情況),線程對象處于鎖池畅形,鎖池的中線程對象獲取到同步鎖之后就可以進(jìn)入可運行狀態(tài)運行养距,即阻塞狀態(tài)的線程獲取同步鎖成功后就可以運行
        //等待和計時等待狀態(tài):sleep(毫秒數(shù))只能等待自動喚醒,wait(毫秒數(shù))可以等待自動喚醒或notify()/notifyAll()喚醒日熬,wait()只能notify()/notifyAll()喚醒棍厌,三種等待都是處于等待池中
        //wait()和wait(毫秒數(shù))釋放同步鎖,其間不占用同步鎖,sleep(毫秒數(shù))占用同步鎖耘纱,其間不釋放同步鎖       
        printStatus(notifyThread); // 現(xiàn)在處于notifyThread sleep(6000) 睡眠6秒過程中
                                    // notifyThread 為計時等待狀態(tài),islive為true
        Thread.sleep(6000); // 主線程睡眠6s 讓兩個子程序有足夠的時間進(jìn)入終止?fàn)顟B(tài)
        // 終止?fàn)顟B(tài) 主線程給了6s 足夠進(jìn)入終止?fàn)顟B(tài)了
        printStatus(waitThread); // waitThread 進(jìn)入終止?fàn)顟B(tài)敬肚,isAlive()為false
        printStatus(notifyThread); // notifyThread 進(jìn)入終止?fàn)顟B(tài),isAlive()為false
        // 注意:線程一旦進(jìn)入終止?fàn)顟B(tài)束析,只能結(jié)束艳馒,無法再返回來
    }
}

輸出1:

Thread-0  當(dāng)前的狀態(tài)為:NEW  isAlive()返回為:false
Thread-1  當(dāng)前的狀態(tài)為:NEW  isAlive()返回為:false
Thread-0  當(dāng)前的狀態(tài)為:RUNNABLE  isAlive()返回為:true
Thread-0  被啟動,開始執(zhí)行...
Thread-0  開始sleep 2s
Thread-0  當(dāng)前的狀態(tài)為:TIMED_WAITING  isAlive()返回為:true
Thread-0  從sleep中醒來
Thread-0  進(jìn)入wait
Thread-0  當(dāng)前的狀態(tài)為:WAITING  isAlive()返回為:true
Thread-1  當(dāng)前的狀態(tài)為:RUNNABLE  isAlive()返回為:true
Thread-1  被啟動员寇,開始執(zhí)行...
Thread-1  向正在wait當(dāng)前對象的線程發(fā)出notify
Thread-1  開始sleep 6s弄慰,并且不會釋放當(dāng)前對象的鎖
Thread-0  當(dāng)前的狀態(tài)為:BLOCKED  isAlive()返回為:true
Thread-1  當(dāng)前的狀態(tài)為:TIMED_WAITING  isAlive()返回為:true
Thread-1  從sleep中醒來
Thread-0  next to Block.wait(), in the loop
Thread-0  next to WaitRunnable.waitOp(), in the loop
Thread-0  out of wait loop
Thread-0  任務(wù)執(zhí)行結(jié)束,即將終止...
Thread-0  當(dāng)前的狀態(tài)為:TERMINATED  isAlive()返回為:false
Thread-1  當(dāng)前的狀態(tài)為:TERMINATED  isAlive()返回為:false

小結(jié):本程序(已經(jīng)注釋的比較清楚了丁恭,這里不再贅余)使用synchronized+標(biāo)志位waitStatus+wait()+notify()/notifyAll()曹动,程序中的兩個線程waitThread和notifyThread,既使用到了線程同步+線程通信牲览,又監(jiān)聽了六個狀態(tài),幫助讀者很好的理解Java線程生命周期六個狀態(tài)恶守。

2.3 線程生命周期的相關(guān)問題

問題(1):線程有幾種狀態(tài)第献?狀態(tài)之間如何轉(zhuǎn)換?

回答(1):沒有絕對的答案兔港,本文采用Java官方的解釋庸毫,6種狀態(tài),分別是 :NEW 新建狀態(tài) RUNNABLE 可運行狀態(tài)(包括ready就緒狀態(tài)+running運行狀態(tài)) TIMED_WAITING 計時等待狀態(tài)
WAITING 等待狀態(tài) BLOCKED 阻塞狀態(tài) TERMINATED 終止?fàn)顟B(tài)(進(jìn)入終止?fàn)顟B(tài)后只能結(jié)束衫樊,無法再返回回來)

關(guān)于狀態(tài)之間的轉(zhuǎn)換圖飒赃,我認(rèn)為這張圖最能符合標(biāo)準(zhǔn):

image.png

補(bǔ)充:因為可運行狀態(tài)Runnable包括Ready就緒狀態(tài)和Running運行狀態(tài),但是就緒狀態(tài)進(jìn)入運行狀態(tài)是CPU調(diào)度科侈,程序員無法控制载佳,所本文中對這兩種狀態(tài)不做區(qū)分,統(tǒng)一為可運行狀態(tài)臀栈,兩者中轉(zhuǎn)換如圖:

image.png

問題(2):六種狀態(tài)isAlive()返回值蔫慧?

回答(2):新建狀態(tài)和終止?fàn)顟B(tài)isAlive()為false,表示線程此時不存活;其他四種狀態(tài)isAlive()為true权薯,表示線程此時為存活姑躲。

問題(3):進(jìn)入終止?fàn)顟B(tài)terminate還可以再回到可運行狀態(tài)嗎?

回答(3):不可以盟蚣,進(jìn)入終止?fàn)顟B(tài)線程只能死亡黍析,不可以再回到可運行狀態(tài)。

問題(4):等待狀態(tài)(包括計時等待)和阻塞狀態(tài)的區(qū)別屎开?wait()阐枣、wait(毫秒數(shù))和sleep(毫秒數(shù))區(qū)別?

回答(4):等待狀態(tài)和阻塞狀態(tài)區(qū)別:Java中,每個對象都有兩個池侮繁,鎖池和等待池虑粥,
阻塞狀態(tài)兩種:
I/O阻塞(本程序不涉及,略)和獲取同步鎖失敗而阻塞(本程序當(dāng)前情況)宪哩,線程對象處于鎖池娩贷,鎖池的中線程對象獲取到同步鎖之后就可以進(jìn)入可運行狀態(tài)運行,即阻塞狀態(tài)的線程獲取同步鎖成功后就可以運行锁孟;
等待和計時等待狀態(tài):
進(jìn)入等待狀態(tài)(包括計時等待)方式有三種(sleep(毫秒數(shù))彬祖、wait()、wait(毫秒數(shù)))
sleep(毫秒數(shù))只能等待自動喚醒品抽,wait(毫秒數(shù))可以等待自動喚醒或notify()/notifyAll()喚醒储笑,wait()只能notify()/notifyAll()喚醒,三種等待都是處于等待池中圆恤。
另外突倍,wait()和wait(毫秒數(shù))釋放同步鎖,其間不占用同步鎖盆昙,sleep(毫秒數(shù))占用同步鎖羽历,其間不釋放同步鎖

方法名 線程狀態(tài) 喚醒 狀態(tài)期間
wait() 進(jìn)入等待狀態(tài) 只能notify()/notifyAll()喚醒 釋放同步鎖,其間不占用同步鎖
wait(毫秒數(shù)) 進(jìn)入計時等待狀態(tài) 可以等待自動喚醒或notify()/notifyAll()喚醒 釋放同步鎖淡喜,其間不占用同步鎖
sleep(毫秒數(shù)) 進(jìn)入計時等待狀態(tài) 只能等待自動喚醒 占用同步鎖秕磷,其間不釋放同步鎖

三、線程優(yōu)先級

Java中提供了操作線程優(yōu)先級的方法setter-getter炼团, int getPriority() :返回線程的優(yōu)先級澎嚣。 void setPriority(int newPriority) : 更改線程的優(yōu)先級。 另外瘟芝,提供了三個表現(xiàn)線程優(yōu)先級的常量易桃,

MAX_PRIORITY=10,最高優(yōu)先級

MIN_PRIORITY=1,最低優(yōu)先級

NORM_PRIORITY=5,默認(rèn)優(yōu)先級,

線程優(yōu)先級有1~ 10,10種模狭,程序員也可以直接使用數(shù)字1~10設(shè)置線程優(yōu)先級颈抚。線程優(yōu)先級代碼實現(xiàn)且見代碼1:

代碼——線程優(yōu)先級:

package mypackage;

public class SimplePriorities implements Runnable {
    private int countDown = 5;

    public SimplePriorities(int priority) {
        Thread.currentThread().setPriority(priority);
    }

    public String toString() {
        return Thread.currentThread() + ": " + countDown;
    }

    @Override
    public void run() {

        while (true) {
            System.out.println(this);
            if (--countDown == 0) {
                return;
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new SimplePriorities(Thread.MIN_PRIORITY)).start();
        new Thread(new SimplePriorities(Thread.MAX_PRIORITY)).start();

    }

}

輸出:

Thread[Thread-1,10,main]: 5
Thread[Thread-1,10,main]: 4
Thread[Thread-1,10,main]: 3
Thread[Thread-0,1,main]: 5
Thread[Thread-1,10,main]: 2
Thread[Thread-0,1,main]: 4
Thread[Thread-1,10,main]: 1
Thread[Thread-0,1,main]: 3
Thread[Thread-0,1,main]: 2
Thread[Thread-0,1,main]: 1

小結(jié):我們看到,程序開始的時候嚼鹉,即使在客戶端低優(yōu)先級的線程Thread-0先創(chuàng)建和執(zhí)行贩汉,但是輸出結(jié)果中,高優(yōu)先級的線程Thread-1可以獲得更多執(zhí)行機(jī)會(前面5個锚赤,Thread-1打印了4個匹舞,Thread-0打印1個,后面5個沒有意義线脚,因為Thread-1已經(jīng)快打印完了赐稽,每個線程countDown總數(shù)只有5)(注意:打印結(jié)果不同且當(dāng)前線程數(shù)和countDown數(shù)字較小叫榕,特例沒有意義)

注意1:值得注意的是,線程優(yōu)先級并不是指線程執(zhí)行的先后順序姊舵,而是線程被執(zhí)行的概率權(quán)重晰绎。事實上,除非程序員使用標(biāo)志位做線程通信括丁,否則Java并沒有提供任何線程執(zhí)行先后順序的機(jī)制荞下,哪個線程先執(zhí)行只取決于CPU調(diào)度。
注意2:此外史飞,不同的操作系統(tǒng)支持的線程優(yōu)先級不同的,建議使用上述三個優(yōu)先級MAX_PRIORITY尖昏、MIN_PRIORITY、NORM_PRIORITY,不要自定義构资。

四抽诉、線程禮讓

線程禮讓是以線程優(yōu)先級為基礎(chǔ)的(本文先介紹線程優(yōu)先級,再介紹線程禮讓)吐绵,線程禮讓yield方法只會給相同優(yōu)先級或者更高優(yōu)先級的線程運行的機(jī)會. 且看如下代碼:

代碼——線程禮讓(在線程優(yōu)先級代碼的基礎(chǔ)上添加):

package mypackage_線程禮讓;
public class SimplePriorities implements Runnable {
    private int countDown = 5;
    public SimplePriorities(int priority) {
        Thread.currentThread().setPriority(priority);
    }
    public String toString() {
        return Thread.currentThread() + ": " + countDown;
    }
    @Override
    public void run() {
        while (true) {
            Thread.yield();
            System.out.println(this);
            if (--countDown == 0) {
                return;
            }
        }
    }
    public static void main(String[] args) {
        new Thread(new SimplePriorities(Thread.MIN_PRIORITY)).start();
        new Thread(new SimplePriorities(Thread.MAX_PRIORITY)).start();
    }
}

輸出:

Thread[Thread-1,10,main]: 5
Thread[Thread-0,1,main]: 5
Thread[Thread-1,10,main]: 4
Thread[Thread-0,1,main]: 4
Thread[Thread-1,10,main]: 3
Thread[Thread-1,10,main]: 2
Thread[Thread-1,10,main]: 1
Thread[Thread-0,1,main]: 3
Thread[Thread-0,1,main]: 2
Thread[Thread-0,1,main]: 1

小結(jié):在run()方法中加上Thread.yield()之后迹淌,可以更大程度上保證高優(yōu)先級的線程打敗低優(yōu)先級的線程,因為yield()只能將線程禮讓給同等優(yōu)先級和更高優(yōu)先級的線程拦赠,本程序中Thread-0會禮讓給Thread-1,但是Thread-1不會禮讓給Thread-0,所以Thread.yield()本身更大程度上保證高優(yōu)先級的線程打敗低優(yōu)先級的線程(注意:不絕對巍沙,況且的這里的countDown計數(shù)器和客戶端線程數(shù)都很少)。

附:線程禮讓yield()和線程休眠sleep()的相同點和不同點
sleep方法和yield方法的區(qū)別:
1):相同點:都能使當(dāng)前處于運行狀態(tài)的線程放棄CPU,把運行的機(jī)會給其他線程.
2):不同點——轉(zhuǎn)讓運行機(jī)會的條件不同:sleep方法會給其他線程運行機(jī)會,但是不考慮其他線程的優(yōu)先級,yield方法只會給相同優(yōu)先級或者更高優(yōu)先級的線程運行的機(jī)會.
3):不同點——轉(zhuǎn)讓后進(jìn)入的狀態(tài)不同:調(diào)用sleep方法后,線程進(jìn)入計時等待狀態(tài),調(diào)用yield方法后,線程進(jìn)入就緒狀態(tài).
4):不同點——兩方法開發(fā)中用途不同:sleep()方法開發(fā)中更多的用于模擬延遲,讓多線程并發(fā)訪問同一個資源的錯誤效果更明顯.荷鼠;yield()方法開發(fā)中很少會使用到該方法,該方法主要用于調(diào)試或測試榔幸,它可能有助于因多線程競爭條件下的錯誤重現(xiàn)現(xiàn)象允乐。

五、后臺線程

5.1 引子:后臺線程

Java中削咆,前臺線程創(chuàng)建的線程默認(rèn)是前臺線程(可以通過setDaenon(true)方法設(shè)置為后臺線程)牍疏,后臺線程創(chuàng)建的線程默認(rèn)是后臺線程。由于main線程是一個前臺線程拨齐,所以我們新建的線程默認(rèn)都是前臺線程鳞陨,本節(jié)闡述Java線程另一種線程——后臺線程。

注意1:設(shè)置后臺線程:thread.setDaemon(true),該方法必須在start方法調(diào)用前瞻惋,否則出現(xiàn)IllegalThreadStateException異常厦滤。
注意2:前臺線程停止,后臺線程也停止歼狼,即使是finally塊的內(nèi)容也不執(zhí)行了掏导,且見本節(jié)代碼演示。
注意3:main線程是前臺線程

5.2 前臺線程與后臺線程

代碼1——前臺線程停止羽峰,后臺線程也停止:

package mypackage;
public class Test {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(new DaemonThread());
        daemonThread.setDaemon(true); // thread.setDaemon(true),該方法必須在start方法調(diào)用前趟咆,否則出現(xiàn)IllegalThreadStateException異常添瓷。
        daemonThread.start();
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(500);
                // mainThread中每次休眠比后臺線程少,這個mainThread就比后臺線程先結(jié)束值纱,從而證明前臺線程結(jié)束后后臺線程也會結(jié)束
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("This is mainThread");
        }
    }
}
class DaemonThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("This is DaemonThread");
        }
    }
}

輸出1:

This is mainThread
This is DaemonThread
This is mainThread
This is mainThread
This is DaemonThread
This is mainThread
This is mainThread

小結(jié)1:該程序(即代碼1)充分說明了鳞贷,前臺線程停止,那么它創(chuàng)建的后臺線程也停止虐唠,但是如果是后臺線程中finally塊里面的內(nèi)容搀愧,會任何時候都得到執(zhí)行嗎?且看代碼2凿滤。

代碼2——前臺線程停止妈橄,后臺線程也停止,即使是finally塊的內(nèi)容也不執(zhí)行了:

package mypackage2;
public class Test {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(new DaemonThread());
        daemonThread.setDaemon(true); // thread.setDaemon(true),該方法必須在start方法調(diào)用前翁脆,否則出現(xiàn)IllegalThreadStateException異常眷蚓。
        daemonThread.start();
    }
}
class DaemonThread implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("This is DaemonThread");
        } finally {
            System.out.println("This is finally Blocks");
        }
    }
}

輸出2:

This is DaemonThread

小結(jié)2:該程序(代碼2)有三種輸出情況:

第一,為空反番,什么都不輸出沙热,這是問題在后臺線程尚未打印This is DaemonThread,主線程就結(jié)束了罢缸;

第二篙贸,輸出為This is DaemonThread\n This is finally Blocks ,這是因為后臺線程完全打印完后,主線程才結(jié)束枫疆;

第三爵川,輸出為This is DaemonThread,這是因為后臺線程尚未執(zhí)行完息楔,finally中的代碼未執(zhí)行完寝贡,主線程結(jié)束。

我們重點談?wù)摰谌N輸出值依,這一個輸出可以充分說明圃泡,后臺線程中即使是finally塊中的語句,也不一定會得到執(zhí)行愿险。這與我們的認(rèn)知的是矛盾的颇蜡,通常來說,finally塊的語句一定會得到執(zhí)行辆亏,所以開發(fā)的時候一般將jdbc連接的close或io的close放在finally塊里面风秤。

原因簡單闡述,這是因為main()退出時褒链,JVM就會立即關(guān)閉所有的后臺進(jìn)場唁情,并不會有任何你希望出現(xiàn)的確認(rèn)形式。

解決方法甫匹,只能讓程序員自己來解決這個問題甸鸟,寫程序的時候惦费,要慎用后臺線程,如代碼2抢韭,若將main()中setDaemon()注釋掉薪贫,就永遠(yuǎn)可以看到finally子句的執(zhí)行,所有要慎用后臺線程刻恭,否則finally子句將不再安全瞧省。

5.3 后臺線程:小結(jié)

后臺線程最重要的點就是生命周期跟隨創(chuàng)建它的前臺線程,前臺線程死亡鳍贾,后臺線程立刻死亡鞍匾,即使是finally中的子句也不一定會執(zhí)行。

六骑科、聯(lián)合線程

6.1 引子:聯(lián)合線程

定義:Java中橡淑,聯(lián)合線程使用join()方法實現(xiàn),線程的join方法表示一個線程等待另一個線程完成后才執(zhí)行咆爽。join方法被調(diào)用之后梁棠,線程對象處于阻塞狀態(tài)。 有人也把這種方式稱為聯(lián)合線程斗埂,就是說把當(dāng)前線程和當(dāng)前線程所在的線程聯(lián)合成一個線程符糊,且見本節(jié)代碼。

6.2 聯(lián)合線程代碼演示

代碼1:

package mypackage;
public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread joinThread = new Thread(new JoinThread());
        for (int i = 0; i < 5; i++) {
            System.out.println("main :" + i);
            if (i == 1) {
                joinThread.start();
            }
            if (i == 3) {
                joinThread.join(); // 強(qiáng)制運行joinThread,main線程被設(shè)置為阻塞狀態(tài)
            }
        }
    }
}
class JoinThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("JoinThread: " + i);
        }
    }
}

輸出1:

main :0
main :1
main :2
main :3
JoinThread: 0
JoinThread: 1
JoinThread: 2
JoinThread: 3
JoinThread: 4
main :4

小結(jié):join()方法就用阻塞當(dāng)前正在運行的線程(代碼1中為main線程)呛凶,運行新建加入的線程(代碼1中的JoinThread),等到JoinThread運行完之后再運行mian線程男娄。可以更加簡單的理解漾稀,就是在main線程中嵌入一個JoinThread線程沪伙。

6.3 聯(lián)合線程:小結(jié)

聯(lián)合線程就是在某線程(代碼1中main線程)中嵌入一個新線程(代碼1中JoinThread),然后運行的時候當(dāng)然是按照嵌入的運行县好。

七、小結(jié)

本文介紹Java線程相關(guān)知識暖混,包括:線程生命周期缕贡、線程優(yōu)先級、線程禮讓拣播、后臺線程晾咪、聯(lián)合線程,基本涵蓋Java多線程部分的各個常用知識點贮配,希望對初學(xué)者學(xué)習(xí)者提供幫助谍倦。
天天打碼,天天進(jìn)步泪勒!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昼蛀,一起剝皮案震驚了整個濱河市宴猾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叼旋,老刑警劉巖仇哆,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異夫植,居然都是意外死亡讹剔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門详民,熙熙樓的掌柜王于貴愁眉苦臉地迎上來延欠,“玉大人,你說我怎么就攤上這事沈跨∮缮樱” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵谒出,是天一觀的道長隅俘。 經(jīng)常有香客問我,道長笤喳,這世上最難降的妖魔是什么为居? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮杀狡,結(jié)果婚禮上蒙畴,老公的妹妹穿的比我還像新娘。我一直安慰自己呜象,他們只是感情好膳凝,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恭陡,像睡著了一般蹬音。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上休玩,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天著淆,我揣著相機(jī)與錄音,去河邊找鬼拴疤。 笑死永部,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的呐矾。 我是一名探鬼主播苔埋,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜒犯!你這毒婦竟也來了组橄?” 一聲冷哼從身側(cè)響起荞膘,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晨炕,沒想到半個月后衫画,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡瓮栗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年削罩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片费奸。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡弥激,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出愿阐,到底是詐尸還是另有隱情微服,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布缨历,位于F島的核電站以蕴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辛孵。R本人自食惡果不足惜丛肮,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魄缚。 院中可真熱鬧宝与,春花似錦、人聲如沸冶匹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嚼隘。三九已至诽里,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間飞蛹,已是汗流浹背须肆。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留桩皿,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓幢炸,卻偏偏與公主長得像泄隔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宛徊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354