多線程系列(二) -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 MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...