多線程系列(二) -Thread類使用詳解

一、簡介

在之前的文章中,我們簡單的介紹了線程誕生的意義和基本概念,採用多線程的編程方式,能充分利用 CPU 資源,顯著的提升程序的執行效率。

其中java.lang.Thread是 Java 實現多線程編程最核心的類,學習Thread類中的方法,是學習多線程的第一步。

下面我們就一起來看看,創建線程的幾種方式以及Thread類中的常用方法。

二、創建線程的方式

在 JDK 1.8 版本中,創建線程總共有四種方式:

  • 繼承 Thread 類
  • 實現 Runnable 接口
  • 使用 Callable 和 Future 創建線程
  • 使用 JDK 8 的 Lambda 創建線程

2.1、通過繼承 Thread 創建線程

通過繼承Thread類來創建線程是最簡單的一種方法,繼承類重寫run()方法,然後通過線程對象實例去調用start()方法即可啓動線程。

public class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "在運行!");
    }
}
MyThread thread = new MyThread();
thread.start();

2.2、通過實現 Runnable 接口創建線程

通過實現Runnable接口來創建線程也是最簡單的一種方法,同時也是最常用的一種方式。

開發者只需要實現Runnable接口,然後通過一個Thread類來啓動。

public class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "在運行!");
    }
}
Thread thread = new Thread(new MyThread());
thread.start();

2.3、使用 Callable 和 Future 創建線程

相比通過實現Runnable接口來創建線程,使用CallableFuture組合來創建線程可以實現獲取子線程執行結果,彌補了調用線程沒有返回值的情況,可以看做是Runnable的一個補充,CallableFuture是 JDK1.5 版本中加入的。

public class MyThread implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "在運行!");
        return Thread.currentThread().getName();
    }
}
Callable<String> callable = new MyThread();
FutureTask<String> ft = new FutureTask<>(callable);
new Thread(ft).start();
// 通過阻塞方式獲取線程執行結果
System.out.println(ft.get());

2.4、使用 JDK 8 的 Lambda 創建線程

Lambda 表達式,是從 JDK1.8 版本開始加入的,可以看作成通過實現Runnable接口創建線程的一種簡寫。

new Thread(()-> System.out.println(Thread.currentThread().getName() + "在運行!")).start();

2.5、創建線程幾種方式的對比

以上四種方式都可以創建線程,使用繼承Thread類的方式創建線程時,編寫簡單,如果需要訪問當前線程,無需使用Thread.currentThread()方法,直接使用this即可獲得當前線程。

採用實現RunnableCallable接口的方式創建線程時,線程類只是實現了 RunnableCallable接口,同時還可以繼承其他類,最後通過Thread類來啓動線程。它也是最常用的一種創建線程方式,通過接口方式來編程,可以實現代碼更加統一。

其實通過繼承Thread類創建線程的方式,本質上也可以看成實現了Runnable接口的一個實例,打開源碼Thread,你會發現這一點。

public class Thread implements Runnable {
    
    //省略...
}

需要特別注意的地方是真正啓動線程的是start()方法而不是run()方法,單獨調用run()方法和調用普通的成員方法一樣,不能啓動線程

三、Thread 常用方法介紹

Thread 類常用的方法主要有三大塊:

  • 構造方法
  • 實例方法
  • 靜態方法

3.1、構造方法

在 JDK 中,Thread 類提供瞭如下幾個常用的構造方法來創建線程。

方法 描述
Thread() 創建一個默認設置的線程實例,線程名稱採用自增ID命名
Thread(Runnable target) 創建一個包含可執行對象的線程實例
Thread(Runnable target, String name) 創建一個包含可執行對象,指定名稱的線程實例
Thread(String name) 創建一個指定名稱的線程實例
Thread(ThreadGroup group, String name) 創建一個指定線程組,線程名稱的線程實例
Thread(ThreadGroup group, Runnable target) 創建一個指定線程組,包含可執行對象的線程實例
Thread(ThreadGroup group, Runnable target, String name) 創建一個指定線程組,包含可執行對象,指定線程名稱的線程實例
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 創建一個指定線程組,包含可執行對象,指定名稱以及堆棧大小的線程實例

其中Thread(Runnable target)構造方法最常見。

Thread thread = new Thread(new Runnable() {
    
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
});
thread.start();

其次Thread(Runnable target, String name)構造方法,可以指定線程名稱。

Thread thread = new Thread(new Runnable() {
    
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}, "thread-demo");
thread.start();

同時,還支持指定線程組來創建線程。

// 創建一個線程組實例
ThreadGroup tg = new ThreadGroup("線程組1");
// 創建一個線程實例
Thread thread = new Thread(tg,new Runnable() {
    
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getThreadGroup().getName() + ":" + Thread.currentThread().getName());
    }
}, "thread-demo");
thread.start();

如果不顯式指定線程組,JVM 會將創建的線程歸到當前線程所屬的線程組中。

關於線程組的相關知識,我們會在後期的系列文章中進行講解。

3.2、實例方法

在 Java 中,實例方法只有實例對象才能調用,也就是new出來的對象或者反射出來的對象,類是無法直接調用的。

在 JDK 中,Thread 類提供瞭如下幾個常用的實例方法來操作線程。

方法 描述
public void start() 啓動線程
public void run() 線程進入可運行狀態時,jvm 會調用該線程的 run 方法;單獨調用 run 方法,不能啓動線程
public final void setName(String name) 設置線程名稱
public final void setPriority(int priority) 設置線程優先級,默認5,取值1-10
public final void setDaemon(boolean on) 設置線程爲守護線程或用戶線程,默認是用戶線程
public final void join(long millisec) 掛起線程 xx 毫秒,參數可以不傳
public void interrupt() 當線程受到阻塞時,調用此方法會拋出一箇中斷信號,讓線程退出阻塞狀態
public final boolean isAlive() 測試線程是否處於活動狀態

下面我們依次來看看它們之間的用法。

3.2.1、start()

start()方法,簡單的說就是啓動線程,至於什麼時候能運行,需要等待獲取 CPU 時間片,然後調用線程對象的run()方法,產生一個異步執行的效果。

樣例代碼如下:

public class ThreadA extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 當前線程:" + Thread.currentThread().getName() + ",正在運行");
        }
    }
}
public class ThreadB extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 當前線程:" + Thread.currentThread().getName() + ",正在運行");
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();

        threadA.start();
        threadB.start();
    }
}

運行結果:

2023-08-30 15:51:43:331 當前線程:Thread-1,正在運行
2023-08-30 15:51:43:331 當前線程:Thread-1,正在運行
2023-08-30 15:51:43:332 當前線程:Thread-0,正在運行
2023-08-30 15:51:43:332 當前線程:Thread-1,正在運行
2023-08-30 15:51:43:332 當前線程:Thread-0,正在運行
2023-08-30 15:51:43:332 當前線程:Thread-1,正在運行
2023-08-30 15:51:43:332 當前線程:Thread-0,正在運行
2023-08-30 15:51:43:332 當前線程:Thread-1,正在運行
2023-08-30 15:51:43:333 當前線程:Thread-0,正在運行
2023-08-30 15:51:43:333 當前線程:Thread-0,正在運行

結果很明顯,CPU 什麼時候執行線程的run()方法具有不確定,同時執行線程順序也具有不確定性,這是採用多線程異步執行程序的一個主要特徵。

3.2.2、run()

如果單獨調用run()方法,不能啓動線程,會像調用普通的成員方法一樣,我們可以將上面例子中的threadA.start()改成threadA.run(),再看看結果如何。

public class ThreadTest {

    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        ThreadB threadB = new ThreadB();

        threadA.run();
        threadB.run();
    }
}

運行結果:

2023-08-30 16:14:50:983 當前線程:main,正在運行
2023-08-30 16:14:50:984 當前線程:main,正在運行
2023-08-30 16:14:50:985 當前線程:main,正在運行
2023-08-30 16:14:50:985 當前線程:main,正在運行
2023-08-30 16:14:50:985 當前線程:main,正在運行
2023-08-30 16:14:50:986 當前線程:main,正在運行
2023-08-30 16:14:50:986 當前線程:main,正在運行
2023-08-30 16:14:50:986 當前線程:main,正在運行
2023-08-30 16:14:50:987 當前線程:main,正在運行
2023-08-30 16:14:50:987 當前線程:main,正在運行

結果很明顯,單獨調用Thread類實例run()方法,是沒有任何異步效果的,全部被主線程執行。

3.2.3、setName()

setName()方法,簡而言之就是設置線程名稱,如果不手動設置,創建線程的時候 JDK 會給一個默認的線程名稱,從 0 開始依次自增。

開發者可以通過getName()方法獲取線程名稱,也可以通過getId()獲取當前線程的唯一標記,這個值用戶無法手動設置,由Thread類自動生成。

樣例代碼如下:

public class ThreadA extends Thread {

    @Override
    public void run() {
        long threadId = Thread.currentThread().getId();
        String threadName = Thread.currentThread().getName();
        System.out.println("threadId:" + threadId + ",threadName:" + threadName);
    }
}
public class ThreadTest {

    public static void main(String[] args)  {
        ThreadA threadA = new ThreadA();
        threadA.setName("thread-a");

        threadA.start();
    }
}

運行結果:

threadId:10,threadName:thread-a
3.2.4、setPriority()

setPriority()方法的作用是設置線程的優先級,取值範圍:1~ 10,與此對應的還有getPriority()方法,用於獲取線程的優先級。優先級越高,擁有優先獲取 CPU 執行的優勢。

換句話說,當有兩個線程在等待 CPU 執行時,優先級高的線程越容易被 CPU 選擇執行。

樣例代碼如下:

public class ThreadA extends Thread {

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        int priority = Thread.currentThread().getPriority();
        System.out.println("threadName:" + threadName + ",priority:" +  priority);
    }
}
public class ThreadB extends Thread {

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        int priority = Thread.currentThread().getPriority();
        System.out.println("threadName:" + threadName + ",priority:" +  priority);
    }
}
public class ThreadTest {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            ThreadA threadA = new ThreadA();
            ThreadB threadB = new ThreadB();

            threadA.start();
            threadB.start();
        }
    }
}

運行結果:

threadName:Thread-0,priority:5
threadName:Thread-1,priority:5
threadName:Thread-2,priority:5
threadName:Thread-3,priority:5
threadName:Thread-4,priority:5
threadName:Thread-5,priority:5
threadName:Thread-6,priority:5
threadName:Thread-7,priority:5
threadName:Thread-8,priority:5
threadName:Thread-9,priority:5

線程默認優先級爲 5,如果不手動指定,那麼線程優先級具有繼承性,比如線程 A 啓動線程 B,那麼線程 B 的優先級和線程 A 的優先級相同。

如果我們手動設置優先級,再看看結果如何。

public class ThreadTest {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            ThreadA threadA = new ThreadA();
            ThreadB threadB = new ThreadB();

            threadA.setPriority(10);
            threadA.start();

            threadB.setPriority(1);
            threadB.start();
        }
    }
}

運行結果:

threadName:Thread-0,priority:10
threadName:Thread-1,priority:10
threadName:Thread-2,priority:10
threadName:Thread-3,priority:10
threadName:Thread-4,priority:1
threadName:Thread-5,priority:10
threadName:Thread-6,priority:1
threadName:Thread-7,priority:1
threadName:Thread-8,priority:1
threadName:Thread-9,priority:1

將線程實例threadB的優先級調整到最高,擁有優先被 CPU 執行的優勢。

在實測過程中,可能有的同學感覺效果並不明顯,如果你的電腦 CPU 是多核的,線程數量較少的情況,可能會被多個 CPU 並行執行,具體執行環境取決於 CPU 。

需要特別注意的是:設置優先級只是很大程度上讓某個線程儘可能獲得比較多的執行機會,操作系統不能保證設置了優先級高的線程就一定會先運行或得到更多的 CPU 時間,具體執行哪一個線程,最終還是由 CPU 來決定

另外有些 linux 操作系統是不區分優先級的,它把所有優先級都視爲 5。

setPriority()方法在實際的開發中,使用的並不多見。

3.2.5、setDaemon()

在 Java 中線程分爲兩種,一種是用戶線程,一種是守護線程。

守護線程是一種特殊的線程,它的作用是爲其他線程的運行提供便利的服務,比如垃圾回收線程,就是最典型的守護線程。

當 JVM 檢測到應用程序中的所有線程都只有守護線程時,它將退出應用程序,因爲沒有存在的必要,服務的對象都沒了,當然就需要銷燬了。

開發者可以通過使用setDaemon()方法,傳遞true作爲參數,使線程成爲一個守護線程,同時可以使用isDaemon()方法來檢查線程是否是守護線程。

樣例代碼如下:

public class ThreadA extends Thread {

    @Override
    public void run() {
        try {
            while (true){
                String threadName = Thread.currentThread().getName();
                boolean isDaemon = Thread.currentThread().isDaemon();
                System.out.println("threadName:" + threadName + ",isDaemon:" + isDaemon);
                Thread.sleep(500);
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        threadA.setDaemon(true);
        threadA.start();

        Thread.sleep(3000);
        System.out.println("主線程方法執行完畢!");
    }
}

運行結果:

threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
threadName:Thread-0,isDaemon:true
主線程方法執行完畢!

需要特別注意的是:創建守護線程時,setDaemon(true)方法必須在線程start()方法之前,否則會拋異常。

3.2.6、join()

join()方法的作用是讓調用此方法的主線程被阻塞,僅當該方法執行完成以後,才能繼續運行。

從概念上感覺很抽象,我們看一下例子!

public class ThreadA extends Thread {

    @Override
    public void run() {
        try {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            System.out.println(time + " 當前線程:" + Thread.currentThread().getName() + ",正在運行");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        threadA.start();

        // 讓執行這個方法的線程阻塞(指的是主線程,不是threadA線程)
        threadA.join();

        String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        System.out.println(time + " 主線程方法執行完畢!");
    }
}

運行結果:

2023-08-31 12:46:06 當前線程:Thread-0,正在運行
2023-08-31 12:46:09 主線程方法執行完畢!

從運行結果可以得出一個結論,主線程main調用threadA.join()方法時,會進入阻塞狀態,直到線程實例threadArun()方法執行完畢,主線程main從阻塞狀態變成可運行狀態。

此例中主線程main會無限期阻塞直到threadA.run()方法執行完畢。

比如某個業務場景下,主線程main的執行時間是 1s,子線程的執行時間是 10s,同時主線程依賴子線程執行完的結果,此時讓主線程執行join()方法進行適度阻塞,可以實現此目標。

3.2.7、interrupt()

interrupt()方法的作用是當線程受到阻塞時,調用此方法會拋出一箇中斷信號,讓線程退出阻塞狀態,如果當前線程沒有阻塞,是無法中斷線程的。

與此對應的還有isInterrupted()方法,用於檢查線程是否已經中斷,但不清除狀態標識。

我們先看一個例子!

public class ThreadA extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 當前線程:" + Thread.currentThread().getName() + ",count:" + i);
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        threadA.start();

        Thread.sleep(50);

        // 檢查線程是否中斷,沒有嘗試終止線程
        if(!threadA.isInterrupted()){
            threadA.interrupt();
        }
    }
}

運行結果:

2023-08-31 14:46:55:053 當前線程:Thread-0,count:0
2023-08-31 14:46:55:054 當前線程:Thread-0,count:1
...
2023-08-31 14:46:55:839 當前線程:Thread-0,count:9999

如果當前線程沒有阻塞,調用interrupt()起不到任何效果。

下面我們對ThreadA類在嘗試改造一下,讓它每執行一次停頓 1 秒,內容如下:

public class ThreadA extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
                System.out.println(time + " 當前線程:" + Thread.currentThread().getName() + ",count:" + i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        threadA.start();

        Thread.sleep(2000);

        // 檢查線程是否中斷,沒有嘗試終止線程
        if(!threadA.isInterrupted()){
            threadA.interrupt();
        }
    }
}

運行結果:

2023-08-31 14:51:19:792 當前線程:Thread-0,count:0
2023-08-31 14:51:20:798 當前線程:Thread-0,count:1
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.example.thread.ThreadA.run(ThreadA.java:22)

很明顯,當線程處於阻塞狀態時,調用interrupt()方法,可以讓線程退出阻塞,起到終止線程的效果。

3.2.8、isAlive()

isAlive()方法的作用是檢查線程是否處於活動狀態,只要線程啓動且沒有終止,方法返回的就是true

看一下例子!

public class ThreadA extends Thread {

    @Override
    public void run() {
        System.out.println("當前線程:" + Thread.currentThread().getName() + ",isAlive:" + Thread.currentThread().isAlive());
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        System.out.println("begin == " + threadA.isAlive());

        threadA.start();

        Thread.sleep(1000);
        System.out.println("end == " + threadA.isAlive());
    }
}

運行結果:

begin == false
當前線程:Thread-0,isAlive:true
end == false

從運行結果上可以看出,線程啓動前isAlive=false,線程運行中isAlive=true,線程運行完成isAlive=false

3.3、靜態方法

在 JDK 中,Thread 類還提供瞭如下幾個常用的靜態方法來操作線程。

方法 描述
public static Thread currentThread() 返回對當前正在執行的線程對象的引用
public static void yield() 暫停當前正在執行的線程對象,並執行其他線程
public static void sleep(long millisec) 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響
public static boolean holdsLock(Object x) 當且僅當當前線程在指定的對象上保持監視器鎖時,才返回 true
public static void dumpStack() 將當前線程的堆棧跟蹤打印至標準錯誤流

下面我們依次來看看它們之間的用法。

3.3.1、currentThread()

currentThread()方法的作用是返回當前正在執行線程對象的引用,在上文中有所介紹。

下面我們再來看看幾個例子!

public class ThreadA extends Thread {

    static {
        System.out.println("靜態塊打印的線程名稱:" + Thread.currentThread().getName());
    }

    public ThreadA() {
        System.out.println("構造方法打印的線程名稱:" + Thread.currentThread().getName());
    }

    @Override
    public void run() {
        System.out.println("run()方法打印的線程名稱:" + Thread.currentThread().getName());
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        threadA.start();
    }
}

運行結果:

靜態塊打印的線程名稱:main
構造方法打印的線程名稱:main
run()方法打印的線程名稱:Thread-0

從運行結果可以看出,線程類的構造方法、靜態塊是被主線程main調用的,而線程類的run()方法纔是用戶線程自己調用的。

再來看看另一個例子!

public class ThreadA extends Thread {

    public ThreadA() {
        System.out.println("構造方法打印 Begin...");
        System.out.println("Thread.currentThread打印的線程名稱:" + Thread.currentThread().getName());
        System.out.println("this.getName打印的線程名稱:" + this.getName());
        System.out.println("構造方法打印 end...");
    }

    @Override
    public void run() {
        System.out.println("run()方法打印 Begin...");
        System.out.println("Thread.currentThread打印的線程名稱:" + Thread.currentThread().getName());
        System.out.println("this.getName打印的線程名稱:" + this.getName());
        System.out.println("run()方法打印 end...");
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        System.out.println("===============");
        threadA.start();
    }
}

運行結果如下:

構造方法打印 Begin...
Thread.currentThread打印的線程名稱:main
this.getName打印的線程名稱:Thread-0
構造方法打印 end...
===============
run()方法打印 Begin...
Thread.currentThread打印的線程名稱:Thread-0
this.getName打印的線程名稱:Thread-0
run()方法打印 end...

從運行結果可以看出,Thread.currentThread方法返回的未必是Thread本身,而是當前正在執行線程對象的引用,這和通過this.XXX()返回的對象是有區別的。

3.3.2、yield()

yield()方法的作用是暫停當前執行的線程對象,並執行其他線程。這個暫停會放棄 CPU 資源,並且放棄 CPU 的時間不確定,有可能剛放棄,就獲得 CPU 資源了,也有可能放棄好一會兒,纔會被 CPU 執行。

相關例子如下。

public class ThreadA extends Thread {

    private String name;

    public ThreadA(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name  + ":" + i);
            if ("t1".equals(name)) {
                System.out.println(name  + ":" + i +"......yield.............");
                Thread.yield();
            }
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA1 = new ThreadA("t1");
        ThreadA threadA2 = new ThreadA("t2");

        threadA1.start();
        threadA2.start();
    }
}

運行結果:

t2:0
t1:0
t2:1
t2:2
t2:3
t2:4
t1:0......yield.............
t1:1
t1:1......yield.............
t1:2
t1:2......yield.............
t1:3
t1:3......yield.............
t1:4
t1:4......yield.............

從運行結果上可以看出,調用yield()方法可以讓線程放棄 CPU 資源,循環次數越多,越明顯。

3.3.3、sleep()

sleep()方法的作用是在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。這個正在執行的線程指的是Thread.currentThread()返回的線程。

根據 JDK API 的說法,該線程不丟失任何監視器的所屬權,換句話說就是不會釋放鎖,如果sleep()代碼上下文被加鎖了,鎖依然在,只是 CPU 資源會讓出給其他線程。

相關例子如下。

public class ThreadA extends Thread {

    @Override
    public void run() {
        try {
            String begin = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(begin + " 當前線程:" + Thread.currentThread().getName());

            Thread.sleep(3000);

            String end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(end + " 當前線程:" + Thread.currentThread().getName());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        threadA.start();
    }
}

運行結果如下:

2023-08-31 18:06:41:459 當前線程:Thread-0
2023-08-31 18:06:44:464 當前線程:Thread-0
3.3.4、holdsLock()

holdsLock()方法表示當且僅當當前線程在指定的對象上保持監視器鎖時,才返回 true,簡單的說就是檢測一個線程是否擁有鎖。

相關例子如下。

public class ThreadA extends Thread {

    private String lock = "lock";

    @Override
    public void run() {
        System.out.println("當前線程:" + Thread.currentThread().getName() + ",Holds Lock = " + Thread.holdsLock(lock));

        synchronized (lock){
            System.out.println("當前線程:" + Thread.currentThread().getName() + ",Holds Lock = " + Thread.holdsLock(lock));
        }
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        threadA.start();
    }
}

運行結果如下:

當前線程:Thread-0,Holds Lock = false
當前線程:Thread-0,Holds Lock = true

關於線程鎖,我們會在後期的文章中進行分享介紹。

3.3.5、dumpStack()

dumpStack()方法的作用是將當前線程的堆棧跟蹤打印至標準錯誤流。此方法僅用於調試。

相關例子如下。

public class ThreadA extends Thread {

    @Override
    public void run() {
        System.out.println("當前線程:" + Thread.currentThread().getName());
        Thread.dumpStack();
    }
}
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadA threadA = new ThreadA();
        threadA.start();
    }
}

運行結果如下:

當前線程:Thread-0
java.lang.Exception: Stack trace
	at java.lang.Thread.dumpStack(Thread.java:1336)
	at com.example.thread.ThreadA.run(ThreadA.java:16)

Thread.dumpStack會將當前線程的堆棧跟蹤信息打印出控制檯。

四、小結

本文主要圍繞線程類Thread相關的常用方法進行詳解,內容難免有所遺漏,歡迎網友留言指出。

五、參考

1、五月的倉頡 - Thread中的實例方法介紹

2、菜鳥教程 - Java 多線程編程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章