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

来源:https://www.cnblogs.com/dxflqm/p/18020462
-Advertisement-
Play Games

在之前的文章中,我們簡單的介紹了線程誕生的意義和基本概念,採用多線程的編程方式,能充分利用 CPU 資源,顯著的提升程式的執行效率。其中java.lang.Thread是 Java 實現多線程編程最核心的類,學習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 多線程編程


作者:程式員志哥
出處:pzblog.cn
資源:微信搜【程式員志哥】關註我,回覆 【技術資料】有我準備的一線程式必備電腦書籍、大廠面試資料和免費電子書。 希望可以幫助大家提升技術和能力。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Java 方法重載 方法重載 允許在同一個類中定義多個具有相同名稱的方法,但 參數列表 必須不同。 語法: returnType methodName(parameter1, parameter2, ..., parameterN) { // 方法體 } 示例: public class Main ...
  • TLDR 修飾變數的時候,可以把 constexpr 對象當作加強版的 const 對象:const 對象表明值不會改變,但不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠 ...
  • 老周一般很少玩游戲,在某寶上買了一堆散件,計劃在過年期間自己做個機械臂耍耍。頭腦中划過一道紫藍色的閃電,想起用游戲手柄來控制機械臂。機械臂是由樹莓派(大草莓)負責控制,然後客戶端通過 Socket UDP 來發送信號。優先考慮在 PC 和手機上測試,就順便折騰一下 XInput API。當然,讀取手 ...
  • 有這樣一個帶有搜索功能的用戶界面需求: 搜索流程如下所示: 這個需求涉及兩個實體: “評分(Rating)、用戶名(Username)”數據與User實體相關 “創建日期(create date)、觀看次數(number of views)、標題(title)、正文(body)”與Story實體相關 ...
  • 常見內置數值類型 數值類型是不可變類型(immutable type),它包括布爾類型、整數、浮點數與複數。 類型 英文名 構造方式 對應關鍵字 構造函數 布爾 Boolean var = True bool bool() 整數 Integer var = 5 int int() 浮點數 Float ...
  • Python 常見內置數據類型 在Python中,常用的類型是這些: Python 中查看數據類型的函數(function)為type()。 >>>text = "Is test a string type object?" >>>print(type(text)) <class 'str'> Py ...
  • ServerCnxnFactory 用於接收客戶端連接、管理客戶端session、處理客戶端請求。 ServerCnxn抽象類 代表一個客戶端連接對象: 從網路讀寫數據 數據編解碼 將請求轉發給上層組件或者從上層組件接收響應 管理連接狀態,比如:enableRecv、sessionTimeout、s ...
  • 本文深入介紹了Java 8的Stream API,包括創建、中間操作、終端操作等,強調了並行流在大數據處理中的性能提升。提供清晰實用的示例,為讀者理解流式計算提供有益指導。 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 微服務架構已經成為搭建高效、可擴展系統的關鍵技術之一,然而,現有許多微服務框架往往過於複雜,使得我們普通開發者難以快速上手並體驗到微服務帶了的便利。為瞭解決這一問題,於是作者精心打造了一款最接地氣的 .NET 微服務框架,幫助我們輕鬆構建和管理微服務應用。 本框架不僅支持 Consul 服務註 ...
  • 先看一下效果吧: 如果不會寫動畫或者懶得寫動畫,就直接交給Blend來做吧; 其實Blend操作起來很簡單,有點類似於在操作PS,我們只需要設置關鍵幀,滑鼠點來點去就可以了,Blend會自動幫我們生成我們想要的動畫效果. 第一步:要創建一個空的WPF項目 第二步:右鍵我們的項目,在最下方有一個,在B ...
  • Prism:框架介紹與安裝 什麼是Prism? Prism是一個用於在 WPF、Xamarin Form、Uno 平臺和 WinUI 中構建鬆散耦合、可維護和可測試的 XAML 應用程式框架 Github https://github.com/PrismLibrary/Prism NuGet htt ...
  • 在WPF中,屏幕上的所有內容,都是通過畫筆(Brush)畫上去的。如按鈕的背景色,邊框,文本框的前景和形狀填充。藉助畫筆,可以繪製頁面上的所有UI對象。不同畫筆具有不同類型的輸出( 如:某些畫筆使用純色繪製區域,其他畫筆使用漸變、圖案、圖像或繪圖)。 ...
  • 前言 嗨,大家好!推薦一個基於 .NET 8 的高併發微服務電商系統,涵蓋了商品、訂單、會員、服務、財務等50多種實用功能。 項目不僅使用了 .NET 8 的最新特性,還集成了AutoFac、DotLiquid、HangFire、Nlog、Jwt、LayUIAdmin、SqlSugar、MySQL、 ...
  • 本文主要介紹攝像頭(相機)如何採集數據,用於類似攝像頭本地顯示軟體,以及流媒體數據傳輸場景如傳屏、視訊會議等。 攝像頭採集有多種方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),網上一些文章以及 ...
  • 前言 Seal-Report 是一款.NET 開源報表工具,擁有 1.4K Star。它提供了一個完整的框架,使用 C# 編寫,最新的版本採用的是 .NET 8.0 。 它能夠高效地從各種資料庫或 NoSQL 數據源生成日常報表,並支持執行複雜的報表任務。 其簡單易用的安裝過程和直觀的設計界面,我們 ...
  • 背景需求: 系統需要對接到XXX官方的API,但因此官方對接以及管理都十分嚴格。而本人部門的系統中包含諸多子系統,系統間為了穩定,程式間多數固定Token+特殊驗證進行調用,且後期還要提供給其他兄弟部門系統共同調用。 原則上:每套系統都必須單獨接入到官方,但官方的接入複雜,還要官方指定機構認證的證書 ...
  • 本文介紹下電腦設備關機的情況下如何通過網路喚醒設備,之前電源S狀態 電腦Power電源狀態- 唐宋元明清2188 - 博客園 (cnblogs.com) 有介紹過遠程喚醒設備,後面這倆天瞭解多了點所以單獨加個隨筆 設備關機的情況下,使用網路喚醒的前提條件: 1. 被喚醒設備需要支持這WakeOnL ...
  • 前言 大家好,推薦一個.NET 8.0 為核心,結合前端 Vue 框架,實現了前後端完全分離的設計理念。它不僅提供了強大的基礎功能支持,如許可權管理、代碼生成器等,還通過採用主流技術和最佳實踐,顯著降低了開發難度,加快了項目交付速度。 如果你需要一個高效的開發解決方案,本框架能幫助大家輕鬆應對挑戰,實 ...