Java併發編程:(2)線程狀態和Thread類詳解

1 線程狀態

線程從創建到最終的消亡整個生命週期要經的狀態:

                       創建(new)、就緒(runnable)、運行(running)、阻塞(blocked、time waiting、waiting)、消亡(dead)

1)創建(new): 當需要新起一個線程來執行某個子任務時,就創建了一個線程。

2)就緒(runnable): 線程創建後不會立即進入就緒狀態,只有線程運行需要的所有條件滿足了,才進入就緒狀態(比如內存資源,瞭解JVM內存區域劃分的都      知道線程私有程序計數器、Java棧、本地方法棧等內存區域)

3)運行(running):進入就緒狀態後要等待獲取CPU時間(CPU可能在忙別的事情),當得到CPU執行時間之後,線程便真正進入運行狀態。

4)阻塞 :線程在運行狀態過程中,可能有多個原因導致當前線程不繼續運行下去,比如用戶主動讓線程sleep睡眠(睡眠一定的時間之後再重新執行)、用        戶主動讓線程等待,或者被同步塊給阻塞,此時就對應着多個狀態:time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻          塞)。

5)消亡(dead):發生中斷或者子任務執行完畢,線程就會被消亡。

狀態轉換   如下圖所示:

 

                                    

2 上下文切換        

      單核CPU同一時刻只能運行一個線程,當在運行一個線程的過程中轉而運行另外一個線程就叫做線程上下文切換(對於進程的上下文切換也是類似的概念);

線程上下文切換過程中需要保存線程的運行狀態,以便下次重新切換回來時能夠繼續切換之前的狀態繼續運行,需要記錄的數據:

     1.程序計數器(上次執行到哪條指令)

      2.CPU寄存器(上次的運行狀態對應的變量值)

總之,線程的上下文切換本質上就是 存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行

線程可以使得任務執行的效率得到提升,但是也會帶來一定程度的系統資源的開銷。

3 Thread類中的方法

java.lang.Thread類的源碼

                 

      Thread類實現了Runnable接口,還有自己的關鍵屬性,比如name(線程名稱)、priority(優先級1-10,默認爲5)、daemon(守護線程標誌)、target(執行任務)。

3.1 Thread類常用方法

  1)start方法:啓動線程,調用start方法後,系統纔會開啓一個新的線程來執行用戶定義的子任務,同時,會爲線程分配資源。

        2)run方法:用戶不需要調用,當start方法啓動線程並獲得CPU執行時間後,便自動執行run方法體中的任務。(所以,繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務)。

        3)sleep方法:交出CPU讓線程睡眠,CPU可執行其他任務。

有兩個重載版本

sleep(long millis)  //參數爲毫秒 

sleep(long millis,int nanoseconds) //第一參數爲毫秒,第二個參數爲納秒

    注意,sleep方法不釋放對象鎖,並且調用sleep方法時,必須捕獲InterruptedException異常或者向上拋出將該異。當線程睡眠時間滿後,不一定會立即得到執行,因爲CPU可能正在執行其他任務。所以說調用sleep方法相當於讓線程進入阻塞狀態

       4)yield方法:該方法會讓當前線程交出CPU權限並去執行其他的線程。類似於sleep不會釋放鎖。但是yield不能控制具體的交出CPU的時間,只能讓相同優先級的線程獲取CPU執行時間的機會。

  注意,調用yield方法並不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只需要等待重新獲取CPU執行時間,這一點是和sleep方法不一樣的。

  5)join方法

  join方法有三個重載版本:

             join()

             join(long millis)   //參數爲毫秒

             join(long millis,int nanoseconds) //第一參數爲毫秒,第二個參數爲納秒

        假如在main線程中,調用thread.join方法,則main方法會等待thread線程執行完畢或者等待一定的時間。

        如果調用的是無參join方法,則等待thread執行完畢;

        如果調用的是指定了時間參數的join方法,則等待一定的時間。

        實際上,調用join方法是調用了Object的wait方法,wait方法會讓線程進入阻塞狀態,並且會釋放線程佔有的鎖,並交出CPU執行權限,所以join方法同樣會讓線程釋放對一個對象持有的鎖。

       6)interrupt方法:中斷;

            一、可以中斷一個正處於阻塞狀態的線程;

            二、通過組合interrupt方法和isInterrupted()方法可停止正在運行的線程。

public class Test {     
    public static void main(String[]args) throws IOException  {
        Test test = new Test();
        MyThread thread = test.new MyThread();
        thread.start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {
             
        }
        thread.interrupt();
    } 
     
    class MyThread extends Thread{
        @Override
        public void run() {
            try {
                System.out.println("進入睡眠狀態");
                Thread.currentThread().sleep(10000);
                System.out.println("睡眠完畢");
            } catch (InterruptedException e) {
                System.out.println("中斷異常");
            }
            System.out.println("執行完畢");
        }
    }
}

執行結果:

         進入睡眠狀態

         中斷異常

         執行完畢

         可知,通過interrupt方法可以中斷處於阻塞狀態的線程

         但是,直接調用interrupt方法不能中斷正在運行中的線程,如果配合isInterrupted()能夠中斷正在運行的線程,因爲調用interrupt方法相當於將中斷標誌位置爲true,那麼可以通過調用isInterrupted()判斷中斷標誌是否被置位來中斷線程的執行。

public class Test {
     
    public static void main(String[]args) throws IOException  {
        Testtest = new Test();
        MyThreadthread = test.new MyThread();
        thread.start();
        try {
            Thread.currentThread().sleep(2000);
        } catch (InterruptedException e) {
             
        }
        thread.interrupt();
    } 
     
    class MyThread extends Thread{
        @Override
        public void run() {
            int i = 0;
            while(!isInterrupted()&& i<Integer.MAX_VALUE){
                System.out.println(i+"while循環");
                i++;
            }
        }
    }
}

         運行會發現,打印若干個值之後,while循環就停止打印了。

         但是不建議這種方式中斷線程,可在MyThread類中增加一個屬性 isStop來標誌是否結束while循環,然後再在while循環中判斷isStop的值來決定是否執行任務。

class MyThread extends Thread{
        private volatile boolean isStop = false;
        @Override
        public void run() {
            int i = 0;
            while(!isStop){
                i++;
            }
        }
         
        public void setStop(boolean stop){
            this.isStop= stop;
        }
    }

    7)stop方法:不安全,已廢棄。因爲調用stop方法會直接終止run方法,並拋出ThreadDeath錯誤,會完全釋放鎖,導致對象狀態不一致。此方法基本不用。

   8)destroy方法:已廢棄,一般不用。  

3.2 Thread類關於線程屬性的方法

  1)getId:用來得到線程ID

  2)getName和setName:獲取或設置線程名稱。

  3)getPriority和setPriority:獲取和設置線程優先級。

  4)setDaemon和isDaemon:設置和判斷線程是否爲守護線程

3.3  線程通過方法調用進行的狀態轉換

Thread類中的方法調用會引起線程狀態發生變化,如下圖:

 

               

4 守護線程和用戶線程的區別

守護線程依賴於創建它的線程,而用戶線程則不依賴。

舉個簡單的例子:如果在main線程中創建了一個守護線程,當main方法運行完畢之後,守護線程也會隨着消亡;但是用戶線程會一直運行完畢,不隨main線程結束而結束。在JVM中,像垃圾收集器線程就依賴於運行的線程,是守護線程。

 

靜態方法Thread.currentThread()用來獲取當前線程。

 

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