Java執行緒狀態及切換

血夜之末發表於2020-11-11

Java執行緒狀態及切換

一、什麼是Java執行緒狀態

在Java程式中,用於描述Java執行緒的六種狀態:

  • 新建(NEW):當前執行緒,剛剛新建出來,尚未啟動。
  • 執行(RUNNABLE):當前執行緒,處於競爭CPU時間分片或已經獲得CPU時間片的狀態。
  • 等待(WAITTING):當前執行緒,處於休眠,不參與CPU時間片競爭的狀態。
  • 定時等待(TIMED_WAITTING):當前執行緒,處於定時休眠,暫時不參與CPU時間片競爭的狀態。
  • 阻塞(BLOCKED):當前執行緒,處於阻塞,不參與CPU時間片競爭的狀態。
  • 終止(TERMINATED):當前執行緒,處於最終停止的狀態。

新建狀態,只能進入執行狀態。而終止狀態無法再轉為其他狀態。

等待/定時等待與阻塞,差別就是後者需要一個事件訊號(如其他執行緒放棄當前執行緒需要的排他鎖),才可以進行狀態切換。當然,強行關閉也是可以的。

Java執行緒的實現並不受JVM規範約束,故不同虛擬機器的實現,往往不同。目前主流的HotSpot是將每個Java執行緒直接對映到一個作業系統的原生執行緒,從而由作業系統完成一系列的執行緒排程

二、哪裡看Java執行緒狀態

檢視Java執行緒狀態,主要存在三種方式:

  • java.lang.Thread.State下可以直接看到Java的六種執行緒狀態
  • Java執行時,程式內部可以通過Thread.getState()獲取目標執行緒狀態
  • Java執行時,程式外部可以通過jstack等工具,檢視執行緒狀態

有關jstack等工具等使用,後續會有部落格,專門闡述。

三、什麼時候變換Java執行緒狀態

Java執行緒狀態的切換嘛。不囉嗦,直接上圖。
在這裡插入圖片描述
這張圖涵蓋了Java執行緒狀態切換的各類方法。相較網上一些圖片,更為詳盡一些。
如果有所遺漏,可以告訴我,我會及時填補上。

四、誰在使用Java執行緒狀態

日常開發中,我們並不會直接與執行緒狀態進行互動。
我們往往直接使用JDK包裝好的工具,如JUC包下的各類工具等。

舉個例子

執行緒池中的應用

位置:com.sun.corba.se.impl.orbutil.threadpool.ThreadPoolImpl#close


    // Note that this method should not return until AFTER all threads have died.
    public void close() throws IOException {

        // Copy to avoid concurrent modification problems.
        List<WorkerThread> copy = null;
        synchronized (workersLock) {
            copy = new ArrayList<>(workers);
        }

        for (WorkerThread wt : copy) {
            wt.close();
            while (wt.getState() != Thread.State.TERMINATED) {
                try {
                    wt.join();
                } catch (InterruptedException exc) {
                    wrapper.interruptedJoinCallWhileClosingThreadPool(exc, wt, this);
                }
            }
        }

        threadGroup = null;
    }

實際檢視JDK後發現,JDK中其實也沒有辣麼多的例項,並且大多數直接關聯執行緒狀態的,也是一些狀態檢視的東東。
這在文章開頭就說,Java執行緒操作,是很底層的,甚至其實現都不包含在虛擬機器規範中。
主流的HotSpot,也是直接將Java執行緒對映到系統執行緒,由系統進行一系列的執行緒排程處理。
所以,在JDK中,是直接對執行緒狀態進行處理的部分是很少的。

五、為什麼需要執行緒狀態

1.為什麼需要執行緒狀態這一概念

這個問題,可以從兩個角度來說明:生命週期與資源管理

  • 一方面,執行緒狀態很好地刻畫了執行緒的整個生命週期,對生命週期中不同階段進行了有效劃分。
  • 另一方面,資源是有限的,需求是無限的。所以需要將系統資源有意識地進行排程,合理利用比較優勢,追求帕累托最優。

實現後者的,就是利用執行緒在生命週期的不同階段這一天然屬性帶來的狀態刻畫,進行的分組。CPU排程只需要關注執行狀態的執行緒。而阻塞,等待等執行緒,都有著屬於自己的一套處理方式。最終獲得資源(開發時對複雜性的應對,執行時對系統資源對消耗,應用者心智模型的成長等)的優化分配。

2.JDK中為什麼需要定義Java執行緒狀態

前文有說到,Java中很少直接使用到執行緒狀態。那麼為什麼還要在JDK中定義Java的六種執行緒狀態呢?
一方面,通過資訊透明的方式,降低使用者使用時建立心智模型的成本。如,現在的我們可以通過列印日誌,打斷點,快速瞭解Java的各個執行緒狀態,並清楚瞭解產生的原因。從而大大提高了我們對Java執行緒的認識,進而更為愉快地擁抱JUC包下諸如執行緒池等工具。
另一方面,通過可以直接應用的狀態列舉,我們可以很好地對現有工具進行二次開發等。如我們可以通過擴充套件AQS,並在其中新增執行緒狀態的校驗,從而得到定製化的執行緒同步工具。

六、如何使用執行緒狀態

使用的方式,我已經在上文說了:學習Java執行緒,定製執行緒相關工具開發。
這裡給出一個有關執行緒學習的demo:


/**
 * @program: learning
 * @description: 用於確認執行緒狀態問題
 * @author: Jarry
 * @create: 2020-10-26 22:25
 **/
public class ThreadState {

    public static void main(String[] args) {
        threadStateTest();
//        threadStateTest2();
//        threadStateWithBlocked();
//        threadStateWithException();
//        threadStateWithSuspend();
    }

    /**
     * 實踐證明:Thread.suspend()與Thread.resume()不會改變執行緒狀態
     * 執行緒狀態該是Waiting,就Waiting。該Timed_Waiting就Timed_Waiting
     * Thread.suspend()與Thread.resume()只是掛起目標執行緒(並且不會釋放鎖資源)
     */
    private static void threadStateWithSuspend() {
        Thread thread1 = new Thread(() -> {
//            LockSupport.park();
            LockSupport.parkNanos(2000000000);
        });

        thread1.start();
        printThreadState(thread1);
        LockSupport.parkNanos(500000000);
        printThreadState(thread1);
        thread1.suspend();
        printThreadState(thread1);
        LockSupport.parkNanos(500000000);
        printThreadState(thread1);
        thread1.resume();
        LockSupport.parkNanos(500000000);
        printThreadState(thread1);

//        LockSupport.unpark(thread1);
    }

    /**
     * 展現執行緒阻塞狀態
     */
    private static void threadStateWithBlocked() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                synchronized (ThreadState.class) {
//                    LockSupport.parkNanos(2000000000);
                    LockSupport.park();
                }
            }
        };

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);

        thread1.start();
        LockSupport.parkNanos(500000000);
        thread2.start();
        // 加上以下時間間隔,則結果:Runnable->Blocked
        // 推論:Thread.start()會將執行緒狀態設定為Runnable,然後在遇到sync的鎖,再切換為Blocked狀態
//        LockSupport.parkNanos(500000000);

        printThreadState(thread2);
        LockSupport.parkNanos(500000000);
        printThreadState(thread2);

        LockSupport.parkNanos(500000000);
        LockSupport.unpark(thread1);
        LockSupport.unpark(thread2);

    }

    /**
     * 由於底層實現機制的不同(相較於其他waiting的方法),無法直接進行Object.wait(),否則會丟擲以下異常
     * @exception java.lang.IllegalMonitorStateException
     * object.wait()進行wait的方法,是直接對其wait_set進行操作
     */
    private static void threadStateWithException() {
        Thread thread1 = new Thread(() -> {
            try {
                ThreadState.class.wait(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });

        thread1.start();
        LockSupport.parkNanos(1000000000);
        printThreadState(thread1);

    }

    /**
     * Object.wait()的使用
     */
    private static void threadStateTest3() {
        Thread thread1 = new Thread(() -> {
            synchronized (ThreadState.class) {
                try {
                    ThreadState.class.wait(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        LockSupport.parkNanos(1000000000);
        printThreadState(thread1);

    }

    /**
     * 確定LockSupport.parkNacos()是否可以生成Time_Waiting狀態
     */
    private static void threadStateTest2() {
        Thread thread1 = new Thread(() -> {
            LockSupport.parkNanos(2000000000);
        });

        thread1.start();
        printThreadState(thread1);
        LockSupport.parkNanos(1000000000);

        printThreadState(thread1);
    }

    /**
     * 檢視到除Blocked以外的所有執行緒狀態
     */
    private static void threadStateTest() {
        Thread thread1 = new Thread(() -> {
            synchronized (ThreadState.class) {
                // Runnable
                printThreadState(Thread.currentThread());

                // 1.Thread.sleep(time)
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                // 2.Object.wait(time)
//                try {
//                    ThreadState.class.wait(2000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                // 3.Thread.join(time)
//                try {
//                    Thread.currentThread().join(2000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                // 4.LockSupport.parkNanos(time);
//            LockSupport.parkNanos(2000000000);
                // 5.LockSupport.parkUntil(timeStamp);
//            LockSupport.parkUntil(System.currentTimeMillis()+2000);

                LockSupport.park();
            }

        });
        thread1.setName("test_thread");

        // New
        printThreadState(thread1);

        thread1.start();
        LockSupport.parkNanos(1000000000);
        // Timed_waiting
        printThreadState(thread1);

        LockSupport.parkNanos(2000000000);
        // Waiting
        printThreadState(thread1);

        LockSupport.unpark(thread1);

        LockSupport.parkNanos(1000000000);
        // Terminated
        printThreadState(thread1);


    }

    private static void printThreadState(Thread thread) {
        System.out.println("current Thread(" + thread.getName()+":" + thread.getId() + ") state:" + thread.getState());
    }

}

程式碼中有一些細碎的知識點,就不在這裡贅述了。感興趣的小夥伴,可以自己看看註釋,自行驗證。

七、擴充套件:系統狀態(三態&五態)

作業系統就包含程式管理,作業管理,檔案管理等。其中程式管理,就涉及系統狀態的三態模型與五態模型。
其中,三態模型包含以下三種狀態:

  • 就緒狀態
  • 執行狀態
  • 阻塞狀態

而五態模型則包含以下五種狀態:

  • 執行狀態
  • 靜止就緒態
  • 活躍就緒態
  • 靜止阻塞態
  • 活躍阻塞態

具體狀態切換等,可以看我之前寫的一篇部落格:
系統架構設計師-作業系統

最後,願與諸君共進步。

八、附錄

補充:WAITTING/TIMED_WAITTING與BLOCKED的區別

其實,我之前也挺糾結這個問題的。
當時的想法是WAITTING/TIMED_WAITTING是由JVM自己維持,而BLOCKED是由系統維持的。後面看到主流的HotSpot是將執行緒對映到系統原生執行緒的,所以這個想法大概率是錯誤的。
那麼兩者區別是什麼呢?
直到我在上文的示例程式碼中,BLOCKED狀態的執行緒,在其他執行緒釋放其所需的鎖時,該執行緒是先轉為RUNNING狀態,再變為其他狀態。這引起我的注意。
最後在stackOverFlow的一篇文章(Difference between WAIT and BLOCKED thread states)中,看到這樣的解釋:

The important difference between the blocked and wait states is the impact on the scheduler. A thread in a blocked state is contending for a lock; that thread still counts as something the scheduler needs to service, possibly getting factored into the scheduler's decisions about how much time to give running threads (so that it can give the threads blocking on the lock a chance).
Once a thread is in the wait state the stress it puts on the system is minimized, and the scheduler doesn't have to worry about it. It goes dormant until it receives a notification. Except for the fact that it keeps an OS thread occupied it is entirely out of play.
This is why using notifyAll is less than ideal, it causes a bunch of threads that were previously happily dormant putting no load on the system to get woken up, where most of them will block until they can acquire the lock, find the condition they are waiting for is not true, and go back to waiting. It would be preferable to notify only those threads that have a chance of making progress.
(Using ReentrantLock instead of intrinsic locks allows you to have multiple conditions for one lock, so that you can make sure the notified thread is one that's waiting on a particular condition, avoiding the lost-notification bug in the case of a thread getting notified for something it can't act on.)

簡單說,就是CPU時間片不會考慮WAITTING/TIMED_WAITTING狀態。
但是,雖然BLOCKED狀態的執行緒無法獲得CPU時間片,但是系統排程時,依舊會考慮BLOCKED狀態的執行緒,將其置於排程計算中。

如果哪位小夥伴對這方面有了解,希望可以聊一聊。

參考

相關文章