JAVA進階系列 - 併發編程 - 第5篇 Thread API

在上一篇中介紹了 Thread 類的構造方法,但是光有構造方法也不夠,咱們還得再學習多一些該類經常使用的 API 才行,這樣才能對該類有更深入的瞭解,同時也能讓咱們有更多的選擇。

Thread類提供的API有幾十個,因爲篇幅問題,本篇文章僅選擇幾個有表明性的來進行講解。剩餘的API小夥伴們感興趣的能夠經過源碼進行查看,也能夠給我留言,咱們共同探討共同窗習。java

目標

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

內容

1.  currentThread

該方法用於返回當前執行線程的引用,咱們能夠在代碼塊中經過它來獲取當前的線程對象,雖然看起來很簡單,可是使用很是普遍,在後續的內容中都會大量使用到該方法。編程

方法:併發

public static native Thread currentThread();

代碼:ide

/**
 * 這個例子咱們能夠看到 Thread.currentThread() 這裏拿到的都是各自的執行線程引用對象。
 */

public class CurrentThreadDemo {
    public static void main(String[] args) {
        // 打印結果爲:true
        Thread t = new Thread(() -> {
            System.out.println("t".equals(Thread.currentThread().getName()));
        }, "t");
        t.start();
        // 打印結果爲:true
        System.out.println("main".equals(Thread.currentThread().getName()));
    }
}

2. setPriority

進程有進程的優先級,線程一樣也有優先級,理論上是優先級比較高的線程會優先被CPU進行調度,但事實上每每並不會如你所願。若是CPU比較忙,設置優先級可能會得到更多的CPU時間片,可是在CPU空閒的狀況下,設置優先級幾乎不會有任何做用。因此,咱們不要試圖在程序設計中使用優先級來綁定某些業務或者讓業務依賴於優先級,這產生的結果可能會與你所指望的結果不一致。高併發

方法:性能

public final void setPriority(int newPriority)// 設置線程優先級
public final int getPriority()// 獲取線程優先級

案例:學習

/**
 * t1 線程的優先級比 t2 線程的低,正常來講應該統計數量會比 t2 線程的少
 * 可是我這裏隨機測試統計到的次數爲:
 * t1: 59573
 * t2: 34321
 * 不一樣的CPU資源狀況會有不一樣的運行結果,小夥伴們能夠多測試幾回看看
 */

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方法屬於一種啓發式的方法,它給調度程序的提示是當前線程願意放棄對處理器的當前使用。調度程序能夠隨意忽略此提示。應將其使用與詳細的性能分析和基準測試結合起來,以確保它實際上具備所需的效果。此方法不多被使用。對於調試或測試目的,它可能頗有用,由於它可能有助於重現因爲競爭條件而產生的錯誤。測試

方法:this

public static native void yield();

案例:spa

/**
 * 將註釋部分放開的話能夠看到控制檯輸出的結果有時候是 0 在前面,有時候是 1 在前面
 * 而將該部分註釋以後,你會發現一隻都是 0 在前面
 */

public class YieldDemo {
    public static void main(String[] args) {
        IntStream.range(02).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是一個靜態方法,根據系統計時器和調度程序的精度和準確性,使當前正在執行的線程進入休眠狀態(暫時中止執行)達指定的毫秒數。該線程不會失去任何監視器的全部權(例如monitor鎖,關於monitor鎖在後續的文章中會進行詳細講解)。

方法:

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

案例:

/**
 * 在這個例子中,咱們分別在自定義線程和主線程中進行了休眠,每一個線程的休眠互不影響
 * Thread.sleep() 只會致使當前線程休眠指定的時間
 */

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 的區別:

  • yield 只是對 CPU 調度器的一個提示,若是生效了,會致使線程上下文的切換;
  • sleep 會致使當前線程暫停指定的時間,沒有 CPU 時間片的消耗;
  • yield 會使線程由 RUNNING 狀態進入 RUNNABLE 狀態;
  • sleep 會使線程短暫 block,以後在給定的時間內釋放 CPU 資源;
  • yield 不能保證必定生效,而 sleep 幾乎百分百的按照指定時間進行休眠(最終休眠時間要以系統的定時器和調度器的精度爲準)
  • yield 沒法捕獲中斷信號,而 sleep 被另外一個線程打斷則能捕獲到中斷信號

5. interrupt

線程interrupt是一個很是重要的API,也是常用的方法,若是在調用:

  1. Object類的 wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int)的方法時阻塞了此線程,此類的sleep(long)或sleep(long,int)方法,則其中斷狀態將被清除,並將收到InterruptedException

  2. InterruptibleChannel 的 I/O 操做,則該通道將被關閉,該線程的中斷狀態將被設置,而且該線程將收到java.nio.channels.ClosedByInterruptException

  3. Selector 的 wakeup 方法,則將設置該線程的中斷狀態,而且它將當即從選擇操做中返回(可能具備非零值),就像調用選擇器的喚醒方法同樣。

與之相關的API還有幾個。

方法:

public void interrupt()// 中斷阻塞
public static boolean interrupted()// 判斷當前線程是否被中斷,該方法會直接擦除掉線程的標識
public boolean isInterrupted()// 判斷當前線程是否被中斷,僅作判斷不影響標識
// interrupted 和 isInterrupted 方法都是調用本地方法 isInterrupted() 來實現,該方法中的參數 ClearInterrupted 主要用來控制是否擦除線程 interrupt 的標識,該標識被擦除後,後續的全部判斷都將會是 false
private native boolean isInterrupted(boolean ClearInterrupted);

案例:

/**
 * 新建一個線程 t,休眠 1分鐘
 * 主線程休眠 2秒鐘以後,對線程 t進行打斷,控制檯輸出:interrupt,程序結束
 */

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();
    }
}

在線程內部中存在一個名爲 interrupt flag 的標識,若是一個線程被 interrupt,那麼它的flag將被設置,在源碼中咱們也能夠看到有對應的描述。

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();
}

可是若是當前線程正在執行可中斷方法被阻塞時,調用interrupt方法將其中斷,反而會致使flag被清楚,關於這點在後面的文章中還會詳細介紹。

7. join

Thread 的 join 方法一樣也是一個很是重要的方法,使用它的特性能夠實現不少比較強的功能,在 Thread 類中給咱們提供了三個不一樣的 方法,具體以下。

方法:

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

案例:

/**
 * 建立兩個線程 1 和 2 並分別啓動,在控制檯進行輸出。
 * 同時main線程調用這兩個線程的方法,這時你會發現線程 1 和 2 會交替的輸出直到兩個線程都運行完畢
 * 此時main線程纔開始循環輸出,若是將 join 方法註釋掉,則三個線程會同時進行輸出
 */

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> list = IntStream.range(13).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));
    }
}

總結

在本篇文章中,咱們學習了 Thread 的一些較常見的 API,Thread 的 API 是掌握高併發編程的基礎,很是有必要熟練掌握!

相關文章
相關標籤/搜索