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

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

Thread類提供的API有幾十個,由于篇幅問題,本篇文章僅選擇幾個有代表性的來進(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)前的線程對象,雖然看起來很簡單,但是使用非常廣泛,在后續(xù)的內(nèi)容中都會大量使用到該方法。

方法:

public static native Thread currentThread();

代碼:

/**
 * 這個例子我們可以看到 Thread.currentThread() 這里拿到的都是各自的執(zhí)行線程引用對象。
 */
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)先級,線程同樣也有優(yōu)先級,理論上是優(yōu)先級比較高的線程會優(yōu)先被CPU進(jìn)行調(diào)度,但事實上往往并不會如你所愿。

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

方法:

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

案例:

/**
 * t1 線程的優(yōu)先級比 t2 線程的低,正常來說應(yīng)該統(tǒng)計數(shù)量會比 t2 線程的少
 * 但是我這里隨機(jī)測試統(tǒng)計到的次數(shù)為:
 *  t1: 59573
 *  t2: 34321
 * 不同的CPU資源情況會有不同的運(yùn)行結(jié)果,小伙伴們可以多測試幾次看看
 */
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)前線程愿意放棄對處理器的當(dāng)前使用。調(diào)度程序可以隨意忽略此提示。應(yīng)將其使用與詳細(xì)的性能分析和基準(zhǔn)測試結(jié)合起來,以確保它實際上具有所需的效果。

此方法很少被使用。對于調(diào)試或測試目的,它可能很有用,因為它可能有助于重現(xiàn)由于競爭條件而產(chǎn)生的錯誤。

方法:

public static native void yield();

案例:

/**
 * 將注釋部分放開的話可以看到控制臺輸出的結(jié)果有時候是 0 在前面,有時候是 1 在前面
 * 而將該部分注釋之后,你會發(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是一個靜態(tài)方法,根據(jù)系統(tǒng)計時器和調(diào)度程序的精度和準(zhǔn)確性,使當(dāng)前正在執(zhí)行的線程進(jìn)入休眠狀態(tài)(暫時停止執(zhí)行)達(dá)指定的毫秒數(shù)。該線程不會失去任何監(jiān)視器的所有權(quán)(例如monitor鎖,關(guān)于monitor鎖在后續(xù)的文章中會進(jìn)行詳細(xì)講解)。

方法:

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

案例:

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

        long startTime = System.currentTimeMillis();
        sleep(3000);
        System.out.printf("%s線程耗時:%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 只是對 CPU 調(diào)度器的一個提示,如果生效了,會導(dǎo)致線程上下文的切換;
  • sleep 會導(dǎo)致當(dāng)前線程暫停指定的時間,沒有 CPU 時間片的消耗;
  • yield 會使線程由 RUNNING 狀態(tài)進(jìn)入 RUNNABLE 狀態(tài);
  • sleep 會使線程短暫 block,之后在給定的時間內(nèi)釋放 CPU 資源;
  • yield 不能保證一定生效,而 sleep 幾乎百分百的按照指定時間進(jìn)行休眠(最終休眠時間要以系統(tǒng)的定時器和調(diào)度器的精度為準(zhǔn))
  • yield 無法捕獲中斷信號,而 sleep 被另一個線程打斷則能捕獲到中斷信號

5. interrupt

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

  1. Object類的 wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int)的方法時阻塞了此線程,此類的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還有幾個。

方法:

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

案例:

/**
 * 新建一個線程 t,休眠 1分鐘
 * 主線程休眠 2秒鐘之后,對線程 t進(jìn)行打斷,控制臺輸出: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)部中存在一個名為 interrupt flag 的標(biāo)識,如果一個線程被 interrupt,那么它的flag將被設(shè)置,在源碼中我們也可以看到有對應(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í)行可中斷方法被阻塞時,調(diào)用interrupt方法將其中斷,反而會導(dǎo)致flag被清楚,關(guān)于這點在后面的文章中還會詳細(xì)介紹。

7. join

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

方法:

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

案例:

/**
 * 創(chuàng)建兩個線程 1 和 2 并分別啟動,在控制臺進(jìn)行輸出。
 * 同時main線程調(diào)用這兩個線程的方法,這時你會發(fā)現(xiàn)線程 1 和 2 會交替的輸出直到兩個線程都運(yùn)行完畢
 * 此時main線程才開始循環(huán)輸出,如果將 join 方法注釋掉,則三個線程會同時進(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é)束了,小伙伴們有什么建議或者意見請聯(lián)系我改進(jìn)哦,你們的支持是我最大的動力?。。?br> 本文由博客群發(fā)一文多發(fā)等運(yùn)營工具平臺 OpenWrite 發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容