Java 線程狀態流轉圖


一.線程狀態流轉圖

  Java的線程可以有多種狀態,在Thread.State類中定義了6個常量來表示線程的狀態,分別是NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED,下面是比較詳細的一幅狀態流轉圖:

  

 

 

 

 

 

二.示例代碼

2.1 sleep

  先看下面一段代碼,測試sleep的時候是否會釋放已經獲取到的資源:

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TestSleep {

    // 創建一個對象,作為synchronized的monitor(鎖)
    private static Object obj = new Object();

    /**
     * 創建線程要完成的操作
     */
    public void work() {
        log.info("進入work,嘗試獲取monitor(obj)");
        // 獲取監視器(monitor)
        synchronized (obj) {
            log.info("獲取到monitor");
            try {
                // 進入的線程進行阻塞
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 測試sleep操作是否會釋放已經獲取的資源
     */
    @Test
    public void test() throws InterruptedException {
        Runnable runnable = this::work;

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);

        t1.start();
        t2.start();

        // 主線程阻塞等待t1和t2執行
        TimeUnit.SECONDS.sleep(1);
        log.info("t1的狀態:{}, t2的狀態:{}", t1.getState(), t2.getState());

        TimeUnit.SECONDS.sleep(100);
    }
}

  運行結果如下:

INFO  2020-06-16 18:08:14 [Thread-2] - 進入work,嘗試獲取monitor(obj)
INFO  2020-06-16 18:08:14 [Thread-1] - 進入work,嘗試獲取monitor(obj)
INFO  2020-06-16 18:08:14 [Thread-2] - 獲取到monitor
INFO  2020-06-16 18:08:15 [main] - t1的狀態:BLOCKED, t2的狀態:TIMED_WAITING
INFO  2020-06-16 18:08:24 [Thread-1] - 獲取到monitor

  從輸出結果可以可以看到,t1和t2同時啟動,t2先進入同步塊,獲取到monitor后,t2繼續執行,進行10秒sleep,休眠過程中t2處於TIMED_WAITING狀態。

  與此同時,由於t1未獲取到monitor,只能阻塞等待t2釋放monitor,此時t1處於BLOCKED狀態;

  等待10秒過后,t1 sleep結束,t1退出同步代碼塊,t2這才獲取到monitor。

  根據上面的例子總結一下,有以下幾點:

  1.線程進行sleep操作時,不會釋放已經獲取的資源;

  2.線程進行sleep時,線程狀態為TIMED_WAITING;

  3.因為沒有獲取到monitor而發生線程阻塞,此時線程處於BLOCKED狀態。

 

2.2 wait與notify

  wait和后面的notify都是針對monitor來說的,可以進行線程間的通信。

  下面介紹

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * 描述:
 *
 * @author ganlixin
 * @create 2020-06-16
 */
@Slf4j
public class TestWaitAndNotify {

    private static Object monitor = new Object();

    @Test
    public void test() throws InterruptedException {

        Runnable doWaitRunnable = this::doWait;
        Runnable doNotifyRunnable = this::doNotify;

        // 兩個線程執行相同的doWait,觀察是否有線程阻塞
        Thread t1 = new Thread(doWaitRunnable);
        Thread t2 = new Thread(doWaitRunnable);
        Thread t3 = new Thread(doNotifyRunnable);

        // t1和t2會進行wait
        t1.start();
        t2.start();

        // 休眠3秒,等待t1和t2運行起來
        TimeUnit.SECONDS.sleep(3);
        log.info("t1的狀態:{}, t2的狀態:{}", t1.getState(), t2.getState());

        // 啟動t3,進行notify
        t3.start();

        TimeUnit.SECONDS.sleep(10);
    }

    private void doWait() {
        log.info("doWait -> 進入到doWait");
        synchronized (monitor) {
            log.info("doWait -> 獲取到monitor");
            try {
                // 阻塞
                monitor.wait();
                log.info("doWait -> wait狀態解除");
            } catch (InterruptedException e) {
                log.info("doWait -> 捕獲InterruptedException,e=", e);
            }
        }
    }

    public void doNotify() {
        log.info("doNotify -> 進入doNotify");
        synchronized (monitor) {
            log.info("doNotify -> 獲取到monitor,開始notify");
            // notify會隨機選擇一個調用了monitor.wait而等待線程
            monitor.notify();

            // notifyAll,會通知所有調用了monitor.wait的線程
            // monitor.notifyAll();
            log.info("doNotify -> notify完成");
        }
    }
}

  運行上面的代碼,運行結果如下:

INFO  2020-06-16 19:07:48 [Thread-2] - doWait -> 進入到doWait
INFO  2020-06-16 19:07:48 [Thread-1] - doWait -> 進入到doWait
INFO  2020-06-16 19:07:48 [Thread-1] - doWait -> 獲取到monitor
INFO  2020-06-16 19:07:48 [Thread-1] - doWait -> 獲取到monitor
INFO  2020-06-16 19:07:51 [main] - t1的狀態:WAITING, t2的狀態:WAITING
INFO  2020-06-16 19:07:51 [Thread-3] - doNotify -> 進入doNotify
INFO  2020-06-16 19:07:51 [Thread-3] - doNotify -> 獲取到monitor,開始notify
INFO  2020-06-16 19:07:51 [Thread-3] - doNotify -> notify完成
INFO  2020-06-16 19:07:51 [Thread-2] - doWait -> wait狀態解除

  從上面的運行結果可以看出,2個線程同時執行doWait,雖然有同步塊synchronized(monitor),但是兩個線程並沒有發生阻塞,幾乎同時獲取到了monitor,獲取到monitor后,t1和t2執行wait操作,此時兩個線程的狀態為WAITING狀態;t3啟動后,執行doNotify,同樣能夠獲取都monitor,並進行notify操作,notify后,t2的wait狀態接觸。

  根據這個例子,總結一下:

  1.調用monitor的wait方法,會釋放synchronized中的monitor,所以其他線程也可以獲取到monitor,這點與sleep不同;

  2.調用monitor.wait方法后,線程處於WAITING狀態;

  3.可以調用monitor.notify()或者monitor.notifyAll()來通知調用了monitor.wait()的線程,只不過notify()只會通知一個線程,並且這個隨意選擇一個線程通知,並不是按照wait順序進行notify。

 

2.3suspend與resume

  suspend掛起線程,resume讓一個掛起的線程繼續執行

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TestSuspendAndResume {

    private static Object monitor = new Object();

    @Test
    public void test() throws InterruptedException {
        Runnable runnable = this::doWork;

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);

        t1.start();
        t2.start();

        // 主線程休眠3秒,等待t1和t2執行
        TimeUnit.SECONDS.sleep(3);
        log.info("t1狀態:{}, t2狀態:{}", t1.getState(), t2.getState());

        log.info("開始resume t1");
        t1.resume();
        TimeUnit.SECONDS.sleep(1);
        log.info("t1狀態:{}", t1.getState());

        log.info("開始resume t2");
        t2.resume();
        TimeUnit.SECONDS.sleep(1);
        log.info("t2狀態:{}", t2.getState());

        TimeUnit.SECONDS.sleep(2);
        log.info("t1狀態:{}, t2狀態:{}", t1.getState(), t2.getState());
    }

    public void doWork() {
        log.info("doWork -> 進入doWork,嘗試獲取monitor");
        synchronized (monitor) {
            log.info("doWork -> 獲取到monitor");

            // suspend掛起線程,接口已經是deprecated
            Thread.currentThread().suspend();
            log.info("doWork -> 掛起狀態解除");
        }
    }
}

  運行結果,這里運行了10次,發現會有不同的結果:

  運行結果1如下

INFO  2020-06-16 19:52:47 [Thread-1] - doWork -> 進入doWork,嘗試獲取monitor
INFO  2020-06-16 19:52:47 [Thread-2] - doWork -> 進入doWork,嘗試獲取monitor
INFO  2020-06-16 19:52:47 [Thread-1] - doWork -> 獲取到monitor
INFO  2020-06-16 19:52:50 [main] - t1狀態:RUNNABLE, t2狀態:BLOCKED
INFO  2020-06-16 19:52:50 [main] - 開始resume t1
INFO  2020-06-16 19:52:50 [Thread-1] - doWork -> 掛起狀態解除
INFO  2020-06-16 19:52:50 [Thread-2] - doWork -> 獲取到monitor
INFO  2020-06-16 19:52:51 [main] - t1狀態:TERMINATED
INFO  2020-06-16 19:52:51 [main] - 開始resume t2
INFO  2020-06-16 19:52:51 [Thread-2] - doWork -> 掛起狀態解除
INFO  2020-06-16 19:52:52 [main] - t2狀態:TERMINATED
INFO  2020-06-16 19:52:54 [main] - t1狀態:TERMINATED, t2狀態:TERMINATED

  從這個結果中可以看出,t1和t2同時執行doWork,但是t1先獲取到monitor,進入同步代碼塊,然后讓t1調用suspend,將t1掛起,可以看到suspend讓線程掛起的時候,並沒有釋放擁有的monitor,導致t2阻塞處於BLOCKED狀態,而調用resume后的線程狀態是RUNNABLE狀態。

  注意結果1中可以看到,resume的順序和suspend的順序相同,即t1 suspend后,再resume t1;t2 suspend后,再resume t2;而后面馬上給出的運行結果2,就出現resume順序和suspend順序不相同的情況:

  運行結果2如下:

INFO  2020-06-16 19:58:34 [Thread-2] - doWork -> 進入doWork,嘗試獲取monitor
INFO  2020-06-16 19:58:34 [Thread-1] - doWork -> 進入doWork,嘗試獲取monitor
INFO  2020-06-16 19:58:34 [Thread-2] - doWork -> 獲取到monitor
INFO  2020-06-16 19:58:37 [main] - t1狀態:BLOCKED, t2狀態:RUNNABLE
INFO  2020-06-16 19:58:37 [main] - 開始resume t1
INFO  2020-06-16 19:58:38 [main] - t1狀態:BLOCKED
INFO  2020-06-16 19:58:38 [main] - 開始resume t2
INFO  2020-06-16 19:58:38 [Thread-2] - doWork -> 掛起狀態解除
INFO  2020-06-16 19:58:38 [Thread-1] - doWork -> 獲取到monitor
INFO  2020-06-16 19:58:39 [main] - t2狀態:TERMINATED
INFO  2020-06-16 19:58:41 [main] - t1狀態:RUNNABLE, t2狀態:TERMINATED

  從結果2中可以看出,

  1.t1、t2進入doWork,t2先獲取到monitor進入同步代碼塊,t1由於未獲取到monitor處於BLOCKED狀態,t2由於調用了suspend方法(掛起)而處於RUNNABLE狀態;

  2.先resume t1,但是t1的狀態還是BLOCKED,然后resume t2,t2的掛起狀態解除,t2釋放monitor,t2完成任務后處於TERMINATED狀態;

  3.t1獲取到monitor,t1執行suspend,一直到程序結束,t1都處於RUNNABLE狀態,而不是預期的TERMINATED狀態,這是因為resume t2的操作發生在 suspend t2之前

  根據這段代碼,以及這兩個運行結果,可以得出下面的結論:

  1.調用suspend的線程不會釋放已經擁有的資源;

  2.調用suspend后,且未調用resume時,線程處於RUNNABLE狀態(就緒);

  3.resume可以發生在suspend之前,且不會拋出異常,只不過此時suspend線程將一直保持RUNNABLE狀態。

  4.一定要防止出現resume發生在suspend之前,其實suspend和resume已經被廢棄了,所以不使用這兩個方法就ok了。

 

 

2.4 join

  join可以實現,等待一個線程結束后,再執行后面的操作,看下面的示例代碼:

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TestJoin {

    @Test
    public void test() throws InterruptedException {

        Thread t1 = new Thread() {
            @Override
            public void run() {
                log.info("開始執行");
                try {
                    log.info("進行sleep 3秒");
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("執行完畢");
            }
        };

        Thread t2 = new Thread() {
            @Override
            public void run() {
                log.info("開始執行");
                try {
                    log.info("等待t1完成");
                    t1.join();
                    log.info("已經等到t1完成工作,t2繼續執行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("執行完畢");
            }
        };

        t1.start();
        t2.start();

        TimeUnit.SECONDS.sleep(1);
        log.info("t1狀態:{}, t2狀態:{}", t1.getState(), t2.getState());

        TimeUnit.SECONDS.sleep(10);
    }
}

  運行結果如下:

INFO  2020-06-16 20:27:54 [Thread-1] - 開始執行
INFO  2020-06-16 20:27:54 [Thread-2] - 開始執行
INFO  2020-06-16 20:27:54 [Thread-2] - 等待t1完成
INFO  2020-06-16 20:27:54 [Thread-1] - 進行sleep 3秒
INFO  2020-06-16 20:27:55 [main] - t1狀態:TIMED_WAITING, t2狀態:WAITING
INFO  2020-06-16 20:27:57 [Thread-1] - 執行完畢
INFO  2020-06-16 20:27:57 [Thread-2] - 已經等到t1完成工作,t2繼續執行
INFO  2020-06-16 20:27:57 [Thread-2] - 執行完畢

  從上面的運行結果得出以下結論:

  1.調用t1.join()方法后,需要等待t1線程執行完畢后,t1.join()后面的語句才會執行;

  2.t2在等待t1執行完畢的過程中,t2處於WAINTING狀態。

 

2.5 yield

  yield的功能:讓當前線程“讓”出cpu,注意,只是讓出CPU,而不會釋放已經獲取到資源,看下面的示例即可:

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TestYield {

    private static Object monitor = new Object();

    @Test
    public void test() throws InterruptedException {

        Runnable runnable = this::doWork;

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(2);

        log.info("t1狀態:{}, t2狀態:{}", t1.getState(), t2.getState()); // RUNNABLE
        TimeUnit.SECONDS.sleep(10);
    }

    public void doWork() {
        log.info("doWork -> 進入doWork");
        synchronized (monitor) {
            log.info("doWork -> 獲取到monitor,進行死循環yield");
            while (true) {
                Thread.yield();
            }
        }
    }
}

  運行代碼,結果如下:

INFO  2020-06-16 20:50:48 [Thread-2] - doWork -> 進入doWork
INFO  2020-06-16 20:50:48 [Thread-1] - doWork -> 進入doWork
INFO  2020-06-16 20:50:48 [Thread-2] - doWork -> 獲取到monitor,進行死循環yield
INFO  2020-06-16 20:50:50 [main] - t1狀態:BLOCKED, t2狀態:RUNNABLE

  可以看到,t1和t2同時進入doWork,但是t1先獲取到monitor進入同步代碼塊,然后t1一直死循環進行yield,而yield的時候,並沒有釋放資源(monitor)。

  所以,yield理論上會“禮讓”CPU,但是禮讓CPU的同時,並不會釋放已經獲取的資源。 

 

2.6 park和unpark

  park用於讓當前線程等待(WAITING),unpark將處於等待的線程恢復正常。

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * 描述:
 *
 * @author ganlixin
 * @create 2020-06-16
 */
@Slf4j
public class TestParkAndUnpark {

    @Test
    public void test() throws InterruptedException {

        Thread t1 = new Thread() {
            @Override
            public void run() {
                log.info("開始park");
                LockSupport.park();
                log.info("WAITING狀態解除");
            }
        };
        t1.start();

        // 讓t1先執行幾秒
        TimeUnit.SECONDS.sleep(2);
        log.info("t1的狀態:{}", t1.getState());

        log.info("開始執行unpark t1");
        LockSupport.unpark(t1);
        TimeUnit.SECONDS.sleep(20);
    }
}

  輸出結果如下:

INFO  2020-06-16 23:38:26 [Thread-1] - 開始park
INFO  2020-06-16 23:38:28 [main] - t1的狀態:WAITING
INFO  2020-06-16 23:38:28 [main] - 開始執行unpark t1
INFO  2020-06-16 23:38:28 [Thread-1] - WAITING狀態解除

  從上面的運行結果可以看出,線程調用park后阻塞,線程進入WAITING狀態,當調用unpark的時候,並沒有拋出InterruptedException

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM