成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java并發編程:深入理解Java線程狀態

開發 前端
Java 線程的六種狀態(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)描述了線程從創建到終止的完整生命周期。理解這些狀態及其轉換機制,有助于更好地掌握多線程編程,避免常見的并發問題。

在本文中,我們將深入探討 Java 線程的六種狀態以及它們之間如何相互轉換。線程狀態的轉換就如同生物從出生、成長到最終死亡的過程,也有一個完整的生命周期。

操作系統中的線程狀態

首先,讓我們看看操作系統中線程的生命周期是如何流轉的。

圖片

在操作系統中,線程共有 5 種狀態:

  • 新建(NEW):線程已創建,但尚未開始執行。
  • 就緒(READY):線程等待使用 CPU,在被調度程序調用后可進入運行狀態。
  • 運行(RUNNING):線程正在使用 CPU。
  • 等待(WAITING):線程因等待事件或其他資源(如 I/O)而被阻塞。
  • 終止(TERMINATED):線程已完成執行。

Java 線程的 6 種狀態

Java 中線程狀態的定義與操作系統中的并不完全相同,查看 JDK 中的java.lang.Thread.State可以找到 Java 線程狀態的定義:

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

它們之間的流程關系如下圖所示:

圖片

接下來,我們將對 Java 線程的六種狀態進行深入分析。

NEW(新建)

處于NEW狀態的線程實際上還沒有啟動。也就是說,Thread 實例的start()方法還沒有被調用。可流轉狀態:RUNNABLE

public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {});
        System.out.println(thread.getState());
    }
}

輸出:

NEW

RUNNABLE(可運行)

Java 中的Runable狀態對應操作系統線程狀態中的兩種狀態,分別是RunningReady,也就是說,Java 中處于Runnable狀態的線程有可能正在執行,也有可能沒有正在執行比如正在等待被分配 CPU 資源。

所以,如果一個正在運行的線程是Runnable狀態,當它運行到任務的一半時,執行該線程的 CPU 被調度去做其他事情,導致該線程暫時不運行,它的狀態依然不變,還是Runnable,因為它有可能隨時被調度回來繼續執行任務。可流轉狀態:BLOCKEDWAITINGTIMED_WAITINGTERMINATED在 Java 中,線程通過調用Thread實例的start()方法進入RUNNABLE狀態。

關于start()方法,有兩個問題需要思考一下:

  • 能否對同一個線程重復調用start()方法?
  • 如果一個線程已經執行完畢并處于TERMINATED狀態,是否可以再次調用該線程的start()方法?

為了分析這兩個問題,我們先來看看start()方法的源碼:

public synchronized void start() {
    if (threadStatus!= 0)
        thrownew IllegalThreadStateException();
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

我們可以看到,在start()方法內部,有一個threadStatus變量。如果它不等于 0,調用start()方法將直接拋出異常。

接下來,調用了一個start0()方法,但它是一個本地方法,無法知道方法內如何處理threadStatus。但沒關系,我們可以在調用start()方法后輸出當前狀態,并嘗試再次調用start()方法:

public class ThreadStateDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {});
        System.out.println(thread.getState());
        thread.start(); // 第一次調用
        System.out.println(thread.getState());
        thread.start(); // 第二次調用
    }
}

輸出:

NEW
RUNNABLE
Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Thread.java:708)
    at thread.basic.ThreadStateDemo.main(ThreadStateDemo.java:11)

可以看到,第一次調用start()方法是可以的,但第二次調用會報錯,java.lang.Thread.start(Thread.java:708)指的是狀態檢查失敗:

圖片

查看獲取當前線程狀態的源碼:

public State getState() {
    // 獲取當前線程狀態
    return sun.misc.VM.toThreadState(threadStatus);
}

public static State toThreadState(int var0) {
    if ((var0 & 4) != 0) {
        return State.RUNNABLE;
    } elseif ((var0 & 1024) != 0) {
        return State.BLOCKED;
    } elseif ((var0 & 16) != 0) {
        return State.WAITING;
    } elseif ((var0 & 32) != 0) {
        return State.TIMED_WAITING;
    } elseif ((var0 & 2) != 0) {
        return State.TERMINATED;
    } else {
        return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
    }
}

我們可以看到,只有State.NEW的狀態值被計算為 0。

因此,結合上面的源碼,我們可以得到兩個問題的答案都是不可行的。start()方法只能在NEW狀態下調用。

BLOCKED(阻塞)

處于BLOCKED狀態的線程正在等待鎖的釋放。可流轉狀態:RUNNABLE我們用一個生活中的例子來說明BLOCKED狀態:

假設你去銀行辦理業務。當你來到某個窗口時,發現前面已經有人了。這時,你必須等待前面的人離開窗口,才能辦理業務。

假設你是線程 B,前面的人是線程 A。此時,A 占有了鎖(銀行辦理業務的窗口),B 正在等待鎖的釋放,線程 B 此時就處于 BLOCKED 狀態。

代碼示例如下:

public class BlockCase {
    private synchronized void businessProcessing() {
        try {
            System.out.println("Thread[" + Thread.currentThread().getName() + "] performs business processing");
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        BlockCase blockCase = new BlockCase();
        Thread A = new Thread(blockCase::businessProcessing, "A");
        Thread B = new Thread(blockCase::businessProcessing, "B");
        A.start();
        B.start();
        System.out.println("Thread[" + A.getName() + "] state:" + A.getState());
        System.out.println("Thread[" + B.getName() + "] state:" + B.getState());
    }
}

這里使用Thread.sleep()來模擬業務處理所需的時間。

輸出:

Thread[A] performs business processing
Thread[A] state:RUNNABLE
Thread[B] state:BLOCKED
Thread[B] performs business processing

注意:如果多次執行輸出結果可能不相同,這是因為兩個線程誰先被調度是隨機的

WAITING(等待)

等待狀態。處于等待狀態的線程需要其他線程喚醒才能轉換為RUNNABLE狀態。可流轉狀態:RUNNABLE調用以下三種方法會使線程進入等待狀態:

  • Object.wait():使當前線程進入等待狀態,直到另一個線程喚醒它;
  • Thread.join():等待指定的線程執行完畢。底層調用的是Object實例的wait方法;
  • LockSupport.park():在獲得調用權限之前禁止當前線程進行線程調度。

我們主要解釋Object.wait()Thread.join()的用法。

繼續前面的例子來解釋 WAITING 狀態:

你在銀行等了很久,終于輪到你來辦理業務了。但不幸的是,你到達柜臺后,柜臺的電腦突然壞了。你必須等待維修人員修好電腦后才能繼續辦理業務。

此時,假設你是線程 A,維修人員是線程 B。雖然你已經擁有了鎖(窗口),但你仍然需要釋放鎖。此時,線程 A 的狀態是 WAITING,然后線程 B 獲得鎖并進入 RUNNABLE 狀態。

如果線程 B 沒有主動喚醒線程 A(通過notify()notifyAll()),線程 A 只能一直等待。

Object.wait()

對于這個例子,我們使用wait()notify()實現,如下所示:

public class WaitingCase {
    private synchronized void businessProcessing() {
        try {
            System.out.println("Thread[" + Thread.currentThread().getName() + "] 處理業務,但電腦壞了。");
            // 釋放窗口資源(鎖)
            wait();
            // 業務處理
            System.out.println("Thread[" + Thread.currentThread().getName() + "] 繼續處理業務。");
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private synchronized void repairComputer() {
        System.out.println("Thread[" + Thread.currentThread().getName() + "] 維修電腦。");
        try {
            // 模擬維修
            Thread.sleep(1000);
            System.out.println("Thread[" + Thread.currentThread().getName() + "] 電腦維修好了。");
            notify();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WaitingCase blockedCase = new WaitingCase();
        Thread A = new Thread(blockedCase::businessProcessing, "A");
        Thread B = new Thread(blockedCase::repairComputer, "B");
        A.start();
        Thread.sleep(500); // 用于確保線程 A 先搶到鎖。睡眠時間應該小于維修時間
        B.start();
        System.out.println("Thread[" + A.getName() + "] state:" + A.getState());
        System.out.println("Thread[" + B.getName() + "] state:" + B.getState());
    }
}

輸出:

Thread[A] 處理業務,但電腦壞了。
Thread[B] 維修電腦。
Thread[A] state:WAITING
Thread[B] state:TIMED_WAITING
Thread[B] 電腦維修好了。
Thread[A] 繼續處理業務。

關于wait()方法,這里有一些需要注意的點:

  • 線程在調用wait()方法之前必須持有對象的鎖。
  • 當線程調用wait()方法時,它會釋放當前的鎖,直到另一個線程調用notify()notifyAll()方法喚醒等待鎖的線程。
  • 調用notify()方法只會喚醒一個等待鎖的線程。如果有多個線程在等待鎖,之前調用wait()方法的線程可能不會被喚醒。
  • 調用notifyAll()方法后,所有等待鎖的線程都會被喚醒,但時間片可能不會立即分配給剛剛放棄鎖的線程,這取決于系統的調度。

Thread.join()

join()方法暫停調用線程的執行,直到被調用的對象完成執行。此時,當前線程處于WAITING狀態。

join()方法通常在主線程中使用,以等待其他線程完成后主線程再繼續執行。

現在來銀行辦理業務的人越來越多了,如果每次窗口空閑出來后所有人都會爭搶窗口的話,會造成資源的浪費。

銀行想到了一個辦法。每個來辦理業務的客戶都會得到一個序列號,窗口會依次叫號。只有被叫到的客戶才需要去窗口,否則他們可以留在休息區。

讓我們擴展前面BlockCase中的例子來簡單實現這樣的功能:

public class JoinCase {
    private synchronized void businessProcessing() {
        try {
            System.out.println("Thread[" + Thread.currentThread().getName() + "] 辦理業務。");
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        JoinCase blockedCase = new JoinCase();
        Thread A = new Thread(blockedCase::businessProcessing, "A");
        Thread B = new Thread(blockedCase::businessProcessing, "B");
        Thread C = new Thread(blockedCase::businessProcessing, "C");
        System.out.println("請讓線程 A 到窗口處理業務。");
        A.start();
        A.join();
        System.out.println("請讓線程 B 到窗口處理業務。");
        B.start();
        B.join();
        System.out.println("請讓線程 C 到窗口處理業務。");
        C.start();
    }
}

輸出:

請讓線程 A 到窗口處理業務。
Thread[A] 辦理業務。
請讓線程 B 到窗口處理業務。
Thread[B] 辦理業務。
請讓線程 C 到窗口處理業務。
Thread[C] 辦理業務。

你可以多次嘗試執行這個程序,每次都會得到相同的結果。

TIMED_WAITING(超時等待)

超時等待狀態。線程等待特定的時間,時間到了會自動喚醒。可流轉狀態:RUNNABLE

調用以下方法會使線程進入超時等待狀態:

  • Thread.sleep(long millis):使當前線程睡眠指定的時間,不釋放鎖;
  • Object.wait(long timeout):線程等待指定的時間。在等待期間,可以通過notify()/notifyAll()喚醒;
  • Thread.join(long millis):等待指定線程執行最多millis毫秒。如果millis為 0,則會繼續執行;
  • LockSupport.parkNanos(long nanos):在獲得調用權限之前,禁止當前線程進行線程調度指定的納秒時間;
  • LockSupport.parkUntil(long deadline):與上述類似,也禁止線程調度指定的時間。

我們繼續上面的例子來解釋 TIMED_WAITING 狀態:

當你輪到你辦理業務員時,之前辦理業務的客戶說他忘記處理一個業務,現在需要處理,要求你給他 5 分鐘時間。你同意了然后就去休息區休息,當 5 分鐘過去后,你重新去辦理業務。

此時,你仍然是線程 A,插隊的朋友是線程 B。線程 B 讓線程 A 等待指定的時間,在這段等待期間,A 處于 TIMED_WAITING 狀態。

等待 5 分鐘后,A 自動喚醒,獲得了競爭鎖(窗口)的資格。

可以使用Object.wait(long timeout)方法實現。Object.wait(long timeout)方法與無參數的wait()方法功能相同,都可以被其他線程調用notify()notifyAll()方法喚醒。

public class TimedWaitingCase {

    privatestaticfinal Object lock = new Object();

    public static void main(String[] args) {
        // 線程 A:模擬等待超時
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("線程 A 開始等待,最多等待 5 秒...");
                    // 線程 A 進入 TIMED_WAITING 狀態,等待 5 秒
                    lock.wait(5000);
                    System.out.println("線程 A 等待結束,繼續執行。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 線程 B:模擬在等待期間喚醒線程 A
        Thread threadB = new Thread(() -> {
            synchronized (lock) {
                try {
                    // 線程 B 先睡眠 2 秒,模擬一些處理時間
                    Thread.sleep(2000);
                    System.out.println("線程 B 嘗試喚醒等待的線程 A...");
                    // 喚醒等待的線程 A
                    lock.notify();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 啟動線程 A
        threadA.start();

        // 啟動線程 B
        threadB.start();
    }
}

不同之處在于,帶參數的wait(long)方法即使沒有其他線程喚醒它,也會在指定時間后自動喚醒,使其獲得競爭鎖的資格。

TERMINATED(終止)

再來看看最后一種狀態,Terminated終止狀態,要想進入這個狀態有兩種可能。

  • run()方法執行完畢,線程正常退出。
  • 出現一個沒有捕獲的異常,終止了run()方法,最終導致意外終止。

可流轉狀態:無

總結

Java 線程的六種狀態(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)描述了線程從創建到終止的完整生命周期。理解這些狀態及其轉換機制,有助于更好地掌握多線程編程,避免常見的并發問題。Java 線程狀態與操作系統線程狀態雖有相似之處,但 Java 對其進行了更細粒度的劃分,以適應復雜的并發場景。掌握這些狀態及其轉換,是編寫高效、穩定多線程程序的關鍵。

責任編輯:武曉燕 來源: 程序猿技術充電站
相關推薦

2020-11-13 08:42:24

Synchronize

2020-12-11 07:32:45

編程ThreadLocalJava

2022-10-12 07:53:46

并發編程同步工具

2021-09-18 06:56:01

JavaCAS機制

2019-06-25 10:32:19

UDP編程通信

2019-07-24 16:04:47

Java虛擬機并發

2023-10-27 07:47:58

Java語言順序性

2025-01-10 07:10:00

2021-07-26 07:47:37

無鎖編程CPU

2024-05-17 12:56:09

C#編程線程

2024-03-19 14:14:27

線程開發

2017-12-18 16:33:55

多線程對象模型

2018-03-14 15:20:05

Java多線程勘誤

2024-01-29 15:54:41

Java線程池公平鎖

2023-09-19 22:47:39

Java內存

2009-06-19 14:10:42

Java多態性

2023-10-08 09:34:11

Java編程

2023-10-27 07:47:37

計算機內存模型

2024-01-09 08:28:44

應用多線程技術

2024-06-06 09:58:13

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 免费在线观看av网站 | 国产精品视频一二三 | 5060网一级毛片 | 国产婷婷色一区二区三区 | 日韩在线免费视频 | 国产一区二区三区在线 | 日韩三级电影一区二区 | 国产精品视频久久 | 亚洲 中文 欧美 日韩 在线观看 | 日韩一区二区久久 | 丁香久久 | av网站免费| 精品福利一区 | 精品一区二区在线观看 | 国产一级网站 | 亚洲精品视频导航 | 国产精品国产精品国产专区不片 | 欧美精品一区二区三区在线 | 亚洲精品一区二三区不卡 | 亚洲一区二区三区免费视频 | 91中文字幕在线 | 五月香婷婷 | 中文字幕乱码亚洲精品一区 | 日本欧美国产在线 | 波波电影院一区二区三区 | 欧洲成人 | 亚洲第一色av | 成人国产精品久久久 | 国产精品久久 | 亚洲一区二区三区在线免费 | 男女久久久 | 亚洲夜射 | 91福利在线观看 | 国产精品免费观看视频 | 免费黄色特级片 | 国产色 | 一级黄色片毛片 | 毛片一级网站 | 在线免费亚洲视频 | 久久婷婷香蕉热狠狠综合 | 在线成人|