JAVA進(jìn)階系列 - 并發(fā)編程 - 第5篇 Thread API

在上一篇中介紹了 Thread 類的構(gòu)造方法烹困,可是光有構(gòu)造方法也不夠晶渠,我們還得再學(xué)習(xí)多一些該類常用的 API 才行,這樣才能對(duì)該類有更深刻的了解曲伊,同時(shí)也能讓我們有更多的選擇叽讳。

Thread類提供的API有幾十個(gè),由于篇幅問題坟募,本篇文章僅選擇幾個(gè)有代表性的來進(jìn)行講解岛蚤。剩余的API小伙伴們感興趣的可以通過源碼進(jìn)行查看,也可以給我留言懈糯,我們共同探討共同學(xué)習(xí)涤妒。

目標(biāo)

  1. currentThread
  2. setPriority
  3. yield
  4. sleep
  5. interrupt
  6. interrupted
  7. join

內(nèi)容

1. currentThread

該方法用于返回當(dāng)前執(zhí)行線程的引用,我們可以在代碼塊中通過它來獲取當(dāng)前的線程對(duì)象赚哗,雖然看起來很簡(jiǎn)單她紫,但是使用非常廣泛,在后續(xù)的內(nèi)容中都會(huì)大量使用到該方法蜂奸。

方法:

public static native Thread currentThread();

代碼:

/**
 * 這個(gè)例子我們可以看到 Thread.currentThread() 這里拿到的都是各自的執(zhí)行線程引用對(duì)象犁苏。
 */
public class CurrentThreadDemo {
    public static void main(String[] args) {
        // 打印結(jié)果為:true
        Thread t = new Thread(() -> {
            System.out.println("t".equals(Thread.currentThread().getName()));
        }, "t");
        t.start();
        // 打印結(jié)果為:true
        System.out.println("main".equals(Thread.currentThread().getName()));
    }
}

2. setPriority

進(jìn)程有進(jìn)程的優(yōu)先級(jí),線程同樣也有優(yōu)先級(jí)扩所,理論上是優(yōu)先級(jí)比較高的線程會(huì)優(yōu)先被CPU進(jìn)行調(diào)度,但事實(shí)上往往并不會(huì)如你所愿朴乖。

如果CPU比較忙祖屏,設(shè)置優(yōu)先級(jí)可能會(huì)獲得更多的CPU時(shí)間片,但是在CPU空閑的情況下买羞,設(shè)置優(yōu)先級(jí)幾乎不會(huì)有任何作用袁勺。所以,我們不要試圖在程序設(shè)計(jì)中使用優(yōu)先級(jí)來綁定某些業(yè)務(wù)或者讓業(yè)務(wù)依賴于優(yōu)先級(jí)畜普,這產(chǎn)生的結(jié)果可能會(huì)與你所期望的結(jié)果不一致期丰。

方法:

public final void setPriority(int newPriority); // 設(shè)置線程優(yōu)先級(jí)
public final int getPriority(); // 獲取線程優(yōu)先級(jí)

案例:

/**
 * t1 線程的優(yōu)先級(jí)比 t2 線程的低,正常來說應(yīng)該統(tǒng)計(jì)數(shù)量會(huì)比 t2 線程的少
 * 但是我這里隨機(jī)測(cè)試統(tǒng)計(jì)到的次數(shù)為:
 *  t1: 59573
 *  t2: 34321
 * 不同的CPU資源情況會(huì)有不同的運(yùn)行結(jié)果,小伙伴們可以多測(cè)試幾次看看
 */
public class PriorityDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                System.out.println("t1");
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            while (true) {
                System.out.println("t2");
            }
        }, "t2");
        // 最小值為:1钝荡,中間值為:5街立,最大值為:10
        t1.setPriority(9);
        t2.setPriority(10);

        t1.start();
        t2.start();
    }
}

3. yield

yield方法屬于一種啟發(fā)式的方法,它給調(diào)度程序的提示是當(dāng)前線程愿意放棄對(duì)處理器的當(dāng)前使用埠通。調(diào)度程序可以隨意忽略此提示赎离。應(yīng)將其使用與詳細(xì)的性能分析和基準(zhǔn)測(cè)試結(jié)合起來,以確保它實(shí)際上具有所需的效果端辱。

此方法很少被使用梁剔。對(duì)于調(diào)試或測(cè)試目的,它可能很有用舞蔽,因?yàn)樗赡苡兄谥噩F(xiàn)由于競(jìng)爭(zhēng)條件而產(chǎn)生的錯(cuò)誤荣病。

方法:

public static native void yield();

案例:

/**
 * 將注釋部分放開的話可以看到控制臺(tái)輸出的結(jié)果有時(shí)候是 0 在前面,有時(shí)候是 1 在前面
 * 而將該部分注釋之后渗柿,你會(huì)發(fā)現(xiàn)一只都是 0 在前面
 */
public class YieldDemo {
    public static void main(String[] args) {
        IntStream.range(0, 2).mapToObj(YieldDemo::test).forEach(Thread::start);
    }

    private static Thread test(int index){
        return new Thread(() -> {
            // if (index == 0){
            //     Thread.yield();
            // }
            System.out.println(index);
        });
    }
}

4. sleep

sleep是一個(gè)靜態(tài)方法众雷,根據(jù)系統(tǒng)計(jì)時(shí)器和調(diào)度程序的精度和準(zhǔn)確性,使當(dāng)前正在執(zhí)行的線程進(jìn)入休眠狀態(tài)(暫時(shí)停止執(zhí)行)達(dá)指定的毫秒數(shù)做祝。該線程不會(huì)失去任何監(jiān)視器的所有權(quán)(例如monitor鎖砾省,關(guān)于monitor鎖在后續(xù)的文章中會(huì)進(jìn)行詳細(xì)講解)。

方法:

public static native void sleep(long millis);   // 休眠的毫秒數(shù)
public static void sleep(long millis, int nanos);   // 休眠的毫秒數(shù)與納秒數(shù)

案例:

/**
 * 在這個(gè)例子中混槐,我們分別在自定義線程喝主線程中進(jìn)行了休眠编兄,每個(gè)線程的休眠互不影響
 * Thread.sleep() 只會(huì)導(dǎo)致當(dāng)前線程休眠指定的時(shí)間
 */
public class SleepDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            long startTime = System.currentTimeMillis();
            sleep(2000);
            System.out.printf("%s線程耗時(shí):%d%s", Thread.currentThread().getName(), System.currentTimeMillis() - startTime, "ms");
            System.out.println("");
        }, "t").start();

        long startTime = System.currentTimeMillis();
        sleep(3000);
        System.out.printf("%s線程耗時(shí):%d%s", Thread.currentThread().getName(), System.currentTimeMillis() - startTime, "ms");
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

yield 和 sleep 的區(qū)別:

  • yield 只是對(duì) CPU 調(diào)度器的一個(gè)提示,如果生效了声登,會(huì)導(dǎo)致線程上下文的切換狠鸳;
  • sleep 會(huì)導(dǎo)致當(dāng)前線程暫停指定的時(shí)間,沒有 CPU 時(shí)間片的消耗悯嗓;
  • yield 會(huì)使線程由 RUNNING 狀態(tài)進(jìn)入 RUNNABLE 狀態(tài)件舵;
  • sleep 會(huì)使線程短暫 block,之后在給定的時(shí)間內(nèi)釋放 CPU 資源脯厨;
  • yield 不能保證一定生效铅祸,而 sleep 幾乎百分百的按照指定時(shí)間進(jìn)行休眠(最終休眠時(shí)間要以系統(tǒng)的定時(shí)器和調(diào)度器的精度為準(zhǔn))
  • yield 無法捕獲中斷信號(hào),而 sleep 被另一個(gè)線程打斷則能捕獲到中斷信號(hào)

5. interrupt

線程interrupt是一個(gè)非常重要的API合武,也是經(jīng)常使用的方法临梗,如果在調(diào)用:

  1. Object類的 wait(),wait(long)或wait(long稼跳,int)方法或join()盟庞,join(long),join(long汤善,int)的方法時(shí)阻塞了此線程什猖,此類的sleep(long)或sleep(long票彪,int)方法,則其中斷狀態(tài)將被清除不狮,并將收到InterruptedException降铸。

  2. InterruptibleChannel 的 I/O 操作,則該通道將被關(guān)閉荤傲,該線程的中斷狀態(tài)將被設(shè)置垮耳,并且該線程將收到java.nio.channels.ClosedByInterruptException

  3. Selector 的 wakeup 方法遂黍,則將設(shè)置該線程的中斷狀態(tài)终佛,并且它將立即從選擇操作中返回(可能具有非零值),就像調(diào)用選擇器的喚醒方法一樣雾家。

與之相關(guān)的API還有幾個(gè)铃彰。

方法:

public void interrupt();    // 中斷阻塞
public static boolean interrupted();    // 判斷當(dāng)前線程是否被中斷,該方法會(huì)直接擦除掉線程的標(biāo)識(shí)
public boolean isInterrupted(); // 判斷當(dāng)前線程是否被中斷芯咧,僅做判斷不影響標(biāo)識(shí)
// interrupted 和 isInterrupted 方法都是調(diào)用本地方法 isInterrupted() 來實(shí)現(xiàn)牙捉,該方法中的參數(shù) ClearInterrupted 主要用來控制是否擦除線程 interrupt 的標(biāo)識(shí),該標(biāo)識(shí)被擦除后敬飒,后續(xù)的所有判斷都將會(huì)是 false
private native boolean isInterrupted(boolean ClearInterrupted);

案例:

/**
 * 新建一個(gè)線程 t邪铲,休眠 1分鐘
 * 主線程休眠 2秒鐘之后,對(duì)線程 t進(jìn)行打斷无拗,控制臺(tái)輸出:interrupt带到,程序結(jié)束
 */
public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(60 * 1000);
            } catch (InterruptedException e) {
                System.out.println("interrupt");
            }
        }, "t");
        t.start();

        Thread.sleep(2000);
        t.interrupt();
    }
}

在線程內(nèi)部中存在一個(gè)名為 interrupt flag 的標(biāo)識(shí),如果一個(gè)線程被 interrupt英染,那么它的flag將被設(shè)置揽惹,在源碼中我們也可以看到有對(duì)應(yīng)的描述。

public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

但是如果當(dāng)前線程正在執(zhí)行可中斷方法被阻塞時(shí)四康,調(diào)用interrupt方法將其中斷搪搏,反而會(huì)導(dǎo)致flag被清楚,關(guān)于這點(diǎn)在后面的文章中還會(huì)詳細(xì)介紹闪金。

7. join

Thread 的 join 方法同樣也是一個(gè)非常重要的方法疯溺,使用它的特性可以實(shí)現(xiàn)很多比較強(qiáng)的功能,在 Thread 類中給我們提供了三個(gè)不同的 方法毕泌,具體如下喝检。

方法:

public final void join();   // 永久等待該線程生命周期結(jié)束
public final synchronized void join(long millis);   // 設(shè)置最長(zhǎng)等待毫秒值,為 0 則永久等待
public final synchronized void join(long millis, int nanos); // 設(shè)置最長(zhǎng)等待毫秒值與納秒值撼泛,為 0 則永久等待

案例:

/**
 * 創(chuàng)建兩個(gè)線程 1 和 2 并分別啟動(dòng),在控制臺(tái)進(jìn)行輸出澡谭。
 * 同時(shí)main線程調(diào)用這兩個(gè)線程的方法愿题,這時(shí)你會(huì)發(fā)現(xiàn)線程 1 和 2 會(huì)交替的輸出直到兩個(gè)線程都運(yùn)行完畢
 * 此時(shí)main線程才開始循環(huán)輸出损俭,如果將 join 方法注釋掉,則三個(gè)線程會(huì)同時(shí)進(jìn)行輸出
 */
public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> list = IntStream.range(1, 3).mapToObj(JoinDemo::getThread).collect(Collectors.toList());

        list.forEach(Thread::start);

        for (Thread thread : list) {
            thread.join();
        }

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static Thread getThread(int name){
        return new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "-" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, String.valueOf(name));
    }
}

總結(jié)

在本篇文章中潘酗,我們學(xué)習(xí)了 Thread 的一些較常見的 API杆兵,Thread 的 API 是掌握高并發(fā)編程的基礎(chǔ),非常有必要熟練掌握仔夺!

今天的文章到這里就結(jié)束了琐脏,小伙伴們有什么建議或者意見請(qǐng)聯(lián)系我改進(jìn)哦,你們的支持是我最大的動(dòng)力8淄谩H杖埂!
本文由博客群發(fā)一文多發(fā)等運(yùn)營(yíng)工具平臺(tái) OpenWrite 發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惰蜜,一起剝皮案震驚了整個(gè)濱河市昂拂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抛猖,老刑警劉巖格侯,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異财著,居然都是意外死亡联四,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門撑教,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朝墩,“玉大人,你說我怎么就攤上這事驮履∮阏蓿” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵玫镐,是天一觀的道長(zhǎng)倒戏。 經(jīng)常有香客問我,道長(zhǎng)恐似,這世上最難降的妖魔是什么杜跷? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮矫夷,結(jié)果婚禮上葛闷,老公的妹妹穿的比我還像新娘。我一直安慰自己双藕,他們只是感情好淑趾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忧陪,像睡著了一般扣泊。 火紅的嫁衣襯著肌膚如雪近范。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天延蟹,我揣著相機(jī)與錄音评矩,去河邊找鬼。 笑死阱飘,一個(gè)胖子當(dāng)著我的面吹牛斥杜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沥匈,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蔗喂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了咐熙?” 一聲冷哼從身側(cè)響起弱恒,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體成艘,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年义起,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片师崎。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡默终,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出犁罩,到底是詐尸還是另有隱情齐蔽,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布床估,位于F島的核電站含滴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丐巫。R本人自食惡果不足惜谈况,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望递胧。 院中可真熱鬧碑韵,春花似錦、人聲如沸缎脾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遗菠。三九已至治筒,卻和暖如春屉栓,著一層夾襖步出監(jiān)牢的瞬間舷蒲,已是汗流浹背耸袜。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留牲平,地道東北人堤框。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纵柿,于是被迫代替她去往敵國(guó)和親蜈抓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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