Java線程狀態和關閉線程的正確姿勢


摘自:https://www.cnblogs.com/zhangweicheng/p/11695849.html

 

Java線程狀態和關閉線程的正確姿勢

 

 

1、線程狀態及切換

  Java中的線程有六種狀態,使用線程Thread內的枚舉類來實現,如下,我對每個狀態都進行了一定的解釋。

復制代碼
  public enum State {
        /** 表示一個線程還沒啟用(即未調用start方法)*/
        NEW,

        /**
         * JVM中執行的線程都是處於這個狀態的,但是處於這個狀態不一定在JVM中執行,
         * 也就是說,只有這個狀態有資格被JVM調度從而獲得時間片執行。
         */
        RUNNABLE,

        /**
         * 線程在等待獲取鎖資源從而進入阻塞狀態,
         * 在這個狀態中,其一直監視鎖的動態,隨時准備搶占鎖
         * 若獲得鎖資源,重新進入RUNNABLE狀態
         */
        BLOCKED,

        /**
         * 當調用Object.wait、Thread.join或者LockSupport類的park方法的時候,線程進入此狀態,
         * 該狀態若無其他線程主動喚醒,則無期限的等待。
         * 喚醒的方法包括:Object.notify(喚醒隨機一個)、Object.notifyAll(喚醒全部線程),
         * 被喚醒的線程重新進入RUNNABLE狀態
         */
        WAITING,

        /**
         * 同WAITING狀態,不過不同的是調用的方法加上了時間的限制,
         * 例如:Object.wait(10)、Thread.sleep(10)、Thread.join(10)、LockSupport.parkNanos(10)、LockSupport.parkUntil(10)這些方法
         * 喚醒的方法有兩種:
         *     1、時間過期。
         *     2、其他線程調用了notify或者notifyAll
         *  喚醒之后同樣進入RUNNABLE狀態
         */
        TIMED_WAITING,

        /** 線程的終點(正常死亡或者被終止)*/
        TERMINATED;
    }
復制代碼

   除了NEW和TERMINATED之外,其他的狀態都是可以相互轉換的,其轉換過程如下圖所示

 

  這里特別講一下RUNNABLE狀態,在這個狀態中線程並不一定在執行程序,只有被JVM調度的線程才能獲得執行的時間片,並且只有這個狀態的線程才能夠獲得時間片,換句話說,被JVM調度並且獲得時間片是只屬於處於RUNNABLE狀態線程的權利。為了便於理解,可以將RUNNABLE分成RunnableRunning兩個狀態(當然,你也可以換成其他的,這里我只是自己好理解),那么上面的線程轉換圖就轉變成了下面這樣(參考《Java並發編程的藝術》中的線程狀態圖):

 

   關於線程狀態轉換的例子,可以通過下面的代碼加深理解

復制代碼
public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        // 1.NEW狀態
        Thread thread = new Thread(() -> {
            // 3.進行test對象鎖的爭奪,若搶到鎖則繼續執行,否則進入BLOCKED狀態監控該鎖,重新獲得后進入RUNNABLE 
            synchronized (test) {
                try {
                    // 4.進入TIMED_WAITING狀態,100ms后重新進入RUNNABLE狀態爭奪時間片 
                    Thread.sleep(100);
                    // 5.重新獲得時間片之后,進入WAITING狀態 
                    test.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 6.正常run()方法執行完畢后線程結束,進入TERMINATED 
        });
        // 2.調用start()方法,線程進入RUNNABLE狀態
        thread.start();
    }
}

注:代碼執行的順序為注釋的序號
復制代碼

 2、正確的結束一個線程

  在上面的例子中我們看到線程的run方法正常執行完畢之后線程就正常死亡進入TERMINATED狀態了,那么如果我們有中途停止線程的需求,我們應該如何正確的結束一個線程呢?

  1. 使用interrupt()方法:在線程內部,其定義了一個變量來標識當前線程是否處於被打斷狀態,調用interrupt()方法則使這個狀態變為true。我們采用這個方法加異常處理的方式來結束一個線程。
    復制代碼
      public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 這里的return是必須的,原因后面說明
                    return;
                }
                System.err.println("thread interrupt test...");
            });
            thread.start();
            thread.interrupt();
            System.out.println("main thread end...");
        }

    // 結果圖:異常后面的語句不會打印


      這里關於線程中的打斷標識變量(之后以interrupt稱)需要說明的是,在特定的情況下其狀態會被重置。
       1、線程內部在catch了異常了之后interrupt的狀態會被重置為false。
    2、線程調用了Thread.interrupted()方法之后,interrupt的狀態會被重置為false。如果需要判斷線程是否中斷的話可以使用對象方法isInterrupted(),此方法不會重置。
    所以在剛才的代碼中需要加入return來結束線程,否則的話線程還是會繼續往下執行,如下圖


    使用isInterrupted()實現:
    public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
    int k = 0;
    while (k++ < 10) {
    System.out.println("do something..." + k);
    }
    }
    System.err.println("thread end...");
    });
    thread.start();
    Thread.sleep(1);
    // 主線程流程執行完了,需要停止線程
    thread.interrupt();
    }
    復制代碼

      

  2. 使用標識位來實現:定義一個變量標識線程是否終止,若終止了則退出run方法。跟上面isInterrupted()的實現一樣,不過換成了volatile變量而已。
    復制代碼
    public class Test {
    
        public static volatile boolean interrupted = false;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() -> {
                while (!interrupted) {
                    int k = 0;
                    while (k++ < 10) {
                        if (interrupted) {
                            System.err.println("thread invoke end....");
                            return;
                        }
                        System.out.println("do something..." + k);
                    }
                }
            System.err.println("thread end...");
            });
            thread.start();
            Thread.sleep(1);
            // 主線程流程執行完了,需要停止線程
            interrupted = true;
        }
    }
    // 結果圖
    復制代碼

     

 stop()方法——不正確的線程中斷方法

    在線程提供的方法中還有一個方法可以強制關閉線程——stop()。這個方法可以說是相當的霸道,給人一種“我不管,我就是要你現在立刻死亡(指線程)”的感覺,並且其還會釋放線程所有的鎖資源,這樣可能會導致出現數據不一致從而出現線程不安全的情況,如下面例子。

復制代碼
public class Test {

        public static volatile boolean flag = false;
        public int state = 0;

        public static void main(String[] args) throws InterruptedException {
            Test test = new Test();
            Thread thread = new Thread(() -> {
                synchronized (test) {
                    try {
                        test.state = 1;
                        Thread.sleep(100);
                        if (flag) {
                            test.state = 2;
                        }
                        System.err.println("thread execute finished...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
            Thread.sleep(1);
            thread.stop();
            flag = true;
            System.out.println("state狀態:" + test.state);
        }
}
// 在這段代碼中,進入線程時默認將state賦為1,接着過一段時間后如果觸發了特定條件則把state賦為2,但是在特定條件觸發之前,線程就被終止掉了,這個特定條件雖然符合但卻沒辦法執行,從而導致數據的不一致。
// 結果圖
  
復制代碼

  所以,我們應該采用上面兩種正確的方式而不是stop()來中止線程。此外,stop()方法若在線程start()之前執行,那么在線程啟動的時候就會立即死亡。

 

 

若有不對之處,望各位不吝指教(反正免費,對吧)。

 
分類:  線程


免責聲明!

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



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