線程的狀態及影響線程狀態的一些方法

一、線程的狀態

圖片來源:牛客網 https://www.nowcoder.com/ta/review-java/review?tpId=31&tqId=21081&query=&asc=true&order=&page=13

1. 新建( new ):新創建了一個線程對象。

2. 可運行( runnable ):線程對象創建後,其他線程(比如 main 線程)調用了該對象 的 start ()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲 取 cpu 的使用權 。

3. 運行( running ):可運行狀態( runnable )的線程獲得了 cpu 時間片( timeslice ) ,執行程序代碼。

4. 阻塞( block ):阻塞狀態是指線程因爲某種原因放棄了 cpu 使用權,也即讓出了 cpu timeslice ,暫時停止運行。直到線程進入可運行( runnable )狀態,纔有 機會再次獲得 cpu timeslice 轉到運行( running )狀態。阻塞的情況分三種:

(一). 等待阻塞:運行( running )的線程執行 o . wait ()方法, JVM 會把該線程放 入等待隊列( waitting queue )中。

(二). 同步阻塞:運行( running )的線程在獲取對象的同步鎖時,若該同步鎖 被別的線程佔用,則 JVM 會把該線程放入鎖池( lock pool )中。

(三). 其他阻塞: 運行( running )的線程執行 Thread . sleep ( long ms )或 t . join ()方法,或者發出了 I / O 請求時, JVM 會把該線程置爲阻塞狀態。            當 sleep ()狀態超時、 join ()等待線程終止或者超時、或者 I / O 處理完畢時,線程重新轉入可運行( runnable )狀態。

5. 死亡( dead ):線程 run ()、 main () 方法執行結束,或者因異常退出了 run ()方法,則該線程結束生命週期。死亡的線程不可再次復生。

(查看Thread類的源碼,可以看到在State枚舉類中,有6種狀態,NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,但感覺這兩種說法都可以講得通,並不影響對併發編程的理解。)

二、影響線程狀態的一些方法

sleep,wait,yield,notify,notifyAll,join,stop(已廢棄),suspend(已廢棄),resume(已廢棄),interrupt等,大家學習下面的一些方法時,可以對照上面的幾種狀態圖及其變化來學習。juc包中仍然有一些方法可以改變線程狀態,將在juc包部分總結。

1.終止線程

終止線程有很多方法:(1)當 run 方法正常完成後線程終止。(2)設置一個標誌位,改變標誌位,使run方法結束(3)通過中斷方法終止(4)使用 stop 方法強行終止線程(過期)

終止線程可以利用stop方法,但stop方法已經廢棄,原因是stop方法會使得一個線程在執行過程中突然停止,然後釋放鎖,這就可能導致操作之後得操作讀取到錯誤得數據,例如兩個線程,讀線程1和寫線程2,2獲得鎖時,1不能讀取,要等待2釋放鎖。而假如2在執行過程中,要爲兩個變量a和b賦值,如果在某個時間點,a已經被賦值,而b還未賦新值,這時調用stop方法,可能會使a爲新值,b爲舊值,而1獲得鎖,就會發生讀取錯誤得情況。而要想安全得終止線程,可以設置一個volatile修飾得布爾變量作爲標誌位,這樣在run方法中會讀取這個變量,需要停止線程時,改變該標誌位爲指定停止的值,當run方法中讀取到這個值時,就會停止了,這樣可以保證線程中操作的原子性不被破壞。

2.線程中斷

線程中斷是使得線程終止的一種方式,但是線程中斷不會使得線程立即退出,而是給線程一個通知,告知目標線程,希望它終止,而目標線程收到通知後,如何處理,則由目標線程自己決定(也可以不理睬),這就避免了stop方法可能導致的問題。

下面是《實戰java高併發程序設計》中關於中斷的方法說明:

實例代碼,新建線程,並在主方法中利用interrupt方法中斷,利用isInterrupted方法判斷是否中斷,如果中斷,退出循環,即終止線程運行。如果想使用靜態方法interrupted,可以將

if(Thread.currentThread().isInterrupted())

換爲

if(Thread.interrupted()) //不需要傳入參數,默認中斷當前線程 
public class InterruptTest {
    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(){
            @Override
            public void run(){
                while(true){
                    if(Thread.currentThread().isInterrupted()){
                        System.out.println("Interrupted!");
                        break;
                    }
                    try{
                        Thread.sleep(2000);
                    } catch (InterruptedException e){
                        System.out.println("Interrupted When Sleep");
                        //防止異常被捕獲後中斷標誌被清除
                        Thread.currentThread().interrupt();
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
}

3.等待(wait)和通知(notify)及睡眠(sleep)

wait和notify是爲了支持多線程之間的協作,這兩個方法不是Thread類中的,而是Object類中的,所以理論上任何對象都可以調用這個方法,當一個線程中調用obj.wait()方法,那麼這個線程就會停止執行,並釋放鎖,轉爲等待阻塞狀態,進入這個對象的等待隊列(一個等待隊列中,可能會有多個線程等待這個對象),直到其他線程調用了obj.notify()方法,這個線程可能會被喚醒(從等待隊列中隨機選擇一個線程),而notifyAll會喚醒所有等待的線程,當線程被喚醒後,要做的第一件事不是執行後續代碼,而是嘗試重新獲得obj的監視器,如果暫時無法獲得,這個線程將進入鎖池,等待獲得鎖,獲得鎖後,纔可以繼續運行。

還有一點,這幾個方法不是隨便調用的,必須獲得這個對象的監視器。

如下是一個例子,t1線程等待t2線程喚醒,而且wait方法會釋放鎖,t2線程喚醒t1線程,同時線程自然終止釋放鎖,t1被喚醒,且獲得鎖,繼續運行,直到結束。

假如t1.start()和t2.start()換一下順序,t1會因爲無法被喚醒,而一直處於等待阻塞狀態。

public class SimpleWN {
    final static Object object = new Object();

    public static class T1 extends Thread {


        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + "T1");
                try {
                    System.out.println(System.currentTimeMillis() + "T1 wait for object");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "T1 end");
            }
        }
    }

    public static class T2 extends Thread {


        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + "T2");
                object.notify();
                System.out.println(System.currentTimeMillis() + "T2 end");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        t2.start();
    }
}

sleep 是線程類(Thread)的靜態方法,導致此線程暫停執行指定時間,給執行機會給其他線程,但是監控狀態依然保持,到時後會自動恢復。調用 sleep 不會釋放對象鎖。 sleep()使當前線程進入阻塞狀態,在指定時間內不會執行。wait,notify 和notifyAll 只能在同步控制方法或者同步控制塊裏面使用,而 sleep 可以在任何地方使用。sleep 必須捕獲異常,而 wait,notify 和 notifyAll 不需要捕獲異常。

注意:1.sleep 方法屬於 Thread 類中方法,表示讓一個線程進入睡眠狀態,等待一定的時間之後,自動醒來進入到可運行狀態,不會馬上進入運行狀態,因爲線程調度機制恢復線程的運行也需要時間,一個線程對象調用了 sleep 方法之後,並不會釋放他所持有的所有對象鎖,所以也就不會影響其他進程對象的運行。但在 sleep 的過程中過程中有可能被其他對象調用它的 interrupt(),產生 InterruptedException 異常,如果你的程序不捕獲這個異常,線程就會異常終止,進入 TERMINATED 狀態,如果你的程序捕獲了這個異常,那麼程序就會繼續執行 catch 語句塊(可能還有 finally 語句塊)以及以後的代碼。(見線程中斷的例子)

2.sleep()方法是一個靜態方法,也就是說他只對當前對象有效,通過 t.sleep()讓 t對象進入 sleep,這樣的做法是錯誤的,它只會是使當前線程被 sleep 而不是 t 線程。(很重要,不瞭解的話會發生費解的錯誤。)

4.掛起(suspend)和繼續執行(resume)方法

suspend(已廢棄)和resume(已廢棄)是Thread類中的實例方法,執行了suspend的線程必須等到執行resume方法後才能繼續執行,wait和notify是Object類的方法,且只能在同步方法或同步代碼塊中使用,而suspend和resume在任何地方都可以使用,所以相比而言要方便一些,但是之所以廢棄,是因爲suspend操作是不釋放鎖的,一個線程掛起後,其他線程想要訪問它所佔有的資源,都必須等待。如果這時,resume意外地在suspend()方法之前執行了,那麼被掛起的線程就很可能無法再運行了,就會發生錯誤。如下的代碼運行時,resume會先於suspend執行,t1線程將一直被掛起。

public class SuspendTest {
    public static void main(String[] args){
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    Thread.currentThread().suspend();
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t1.start();
        t1.resume();
    }
}

而想要完成這個功能,可以設置一個標誌位,配個wait和notify方法,可以達到suspend和resume的效果。

5.等待線程結束(join)和謙讓(yield)

join方法是一個Thread類的實例方法,可以讓一個線程等待另一個線程執行完畢後再繼續執行(前者進入等待阻塞狀態),比如一個線程的輸入可能非常依賴於另外一個或者多個線程的輸出,所以需要等待另一個線程執行完畢。這個方法比較好理解,如下例子中,主線程要等待at線程執行完畢後,才能使i的值爲1000000,否則將是一個很小的值。

public class JoinMain {
    public static volatile int i = 0;
    public static class AddThread extends Thread{
    public void run() {
            for(i=0;i<10000000;i++){
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

yield是Thread類的靜態方法,它會使當前線程讓出CPU,從“運行中”狀態變爲“可運行”狀態,當然,讓出CPU後,該線程還會進行CPU資源的爭奪,如果爭奪到CPU,還會繼續運行。

參考:

1.https://www.nowcoder.com/ta/review-java/review?tpId=31&tqId=21081&query=&asc=true&order=&page=13

2.《實戰java高併發程序設計》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章