java線程進階

一、線程的生命週期
      這裏所說的線程的生命週期,也是根據Thread類裏面的方法來定義的。JDK API 1.6裏和生命週期有關的方法有一些幾個:
      1、interrupt():中斷線程。
      2、interrupted():測試當前線程是否已經中斷。
      3、isInterrupted():測試線程是否已經中斷。
      4、join():等待該線程終止。
      5、join(long millis):等待該線程終止的時間最長爲 millis 毫秒。
      6、join(long millis, int nanos):等待該線程終止的時間最長爲 millis 毫秒 + nanos 納秒。
      7、run():如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作並返回。
      8、sleep(long millis, int nanos): 在指定的毫秒數加指定的納秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。
      9、start(): 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。
      10、yield():暫停當前正在執行的線程對象,並執行其他線程。
      11、isAlive(): 測試線程是否處於活動狀態。
      對於官方已經棄用不建議使用的方法沒有列舉,從這些方法中可以看出,目前的java線程生命週期有開始運行、睡眠、等待、中斷、暫停,但是沒有了重新開始。Java的線程是不能重啓的,也就是說,當線程的run()方法執行到最後一行,退出之後,這個線程就結束了,不能再通過start()方法重啓啓動這個線程,只能重新構造一個線程對象,再調用其start()方法來啓動,但這個對象和原來那個對象已經不同了。

     線程在建立後並不馬上執行run方法中的代碼,而是處於等待狀態。線程處於等待狀態時,可以通過Thread類的方法來設置線程不各種屬性,如線程的優先級(setPriority)、線程名(setName)和線程的類型(setDaemon)等。當調用start方法後,線程開始執行run方法中的代碼。線程進入運行狀態。可以通過Thread類的isAlive方法來判斷線程是否處於運行狀態。當線程處於運行狀態時,isAlive返回true,當isAlive返回false時,可能線程處於等待狀態,也可能處於停止狀態。
  1. <SPAN style="FONT-FAMILY: SimSun">public class LifeCycle extends Thread    
  2. {    
  3.     public void run()    
  4.     {    
  5.         int n = 0;    
  6.         while ((++n) < 1000);            
  7.     }    
  8.          
  9.     public static void main(String[] args) throws Exception    
  10.     {    
  11.         LifeCycle thread1 = new LifeCycle();    
  12.         System.out.println("isAlive: " + thread1.isAlive());    
  13.         thread1.start();    
  14.         System.out.println("isAlive: " + thread1.isAlive());    
  15.         thread1.join();  // 等線程thread1結束後再繼續執行      
  16.         System.out.println("thread1已經結束!");    
  17.         System.out.println("isAlive: " + thread1.isAlive());    
  18.     }    
  19. }  </SPAN>  
public class LifeCycle extends Thread  
{  
    public void run()  
    {  
        int n = 0;  
        while ((++n) < 1000);          
    }  
       
    public static void main(String[] args) throws Exception  
    {  
        LifeCycle thread1 = new LifeCycle();  
        System.out.println("isAlive: " + thread1.isAlive());  
        thread1.start();  
        System.out.println("isAlive: " + thread1.isAlive());  
        thread1.join();  // 等線程thread1結束後再繼續執行   
        System.out.println("thread1已經結束!");  
        System.out.println("isAlive: " + thread1.isAlive());  
    }  
}  
要注意一下,在上面的代碼中使用了join方法,這個方法的主要功能是保證線程的run方法完成後程序才繼續運行,上面代碼的運行結果:

isAlive: false
isAlive: true
thread1已經結束!
isAlive: false

  一但線程開始執行run方法,就會一直到這個run方法執行完成這個線程才退出。但在線程執行的過程中,可以通過兩個方法使線程暫時停止執行。這兩個方法是yield和sleep。thread.yield()在多線程程序中,爲了防止某線程獨佔CPU資源(這樣其它的線程就得不到"響應"了).可以讓當前執行的線程"休息"一下.但是這種thread.yield() 調用,並不保證下一個運行的線程就一定不是該線程.而使用sleep使線程休眠後,只能在設定的時間後使線程處於就緒狀態(在線程休眠結束後,線程不一定會馬上執行,只是進入了就緒狀態,等待着系統進行調度)。在使用sleep時要注意,不能在一個線程中來休眠另一個線程。如main方法中使用thread.sleep(2000)方法是無法使thread線程休眠2秒的,而只能使主線程休眠2秒。在使用sleep方法時有四點需要注意:

1. sleep方法有兩個重載形式,其中一個重載形式不僅可以設毫秒,而且還可以設納秒(1,000,000納秒等於1毫秒)。但大多數操作系統平臺上的Java虛擬機都無法精確到納秒,因此,如果對sleep設置了納秒,Java虛擬機將取最接近這個值的毫秒。

2. 在使用sleep方法時必須使用throws或try{...}catch{...}。因爲run方法無法使用throws,所以只能使用try{...}catch{...}。當在線程休眠的過程中,使用interrupt方法(這個方法將在2.3.3中討論)中斷線程時sleep會拋出一個InterruptedException異常。

3. sleep()使當前線程進入停滯狀態,所以執行sleep()的線程在指定的時間內肯定不會執行;yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態後馬上又被執行。

4. sleep()可使優先級低的線程得到執行的機會,當然也可以讓同優先級和高優先級的線程有執行的機會;yield()只能使同優先級的線程有執行的機會。

  1. <SPAN style="FONT-FAMILY: SimSun">class TestThreadMethod extends Thread{    
  2. public static int shareVar = 0;    
  3. public TestThreadMethod(String name){    
  4. super(name);    
  5. }    
  6. public void run(){    
  7. for(int i=0; i<4; i++){    
  8. System.out.print(Thread.currentThread().getName());    
  9. System.out.println(" : " + i);    
  10. //Thread.yield(); (1)     
  11. /* (2) */    
  12. try{    
  13. Thread.sleep(3000);    
  14. }    
  15. catch(InterruptedException e){    
  16. System.out.println("Interrupted");    
  17. }}}    
  18. }    
  19. public class TestThread{    
  20. public static void main(String[] args){    
  21. TestThreadMethod t1 = new TestThreadMethod("t1");    
  22. TestThreadMethod t2 = new TestThreadMethod("t2");    
  23. t1.setPriority(Thread.MAX_PRIORITY);    
  24. t2.setPriority(Thread.MIN_PRIORITY);    
  25. t1.start();    
  26. t2.start();    
  27. }    
  28. } </SPAN>  
class TestThreadMethod extends Thread{  
public static int shareVar = 0;  
public TestThreadMethod(String name){  
super(name);  
}  
public void run(){  
for(int i=0; i<4; i++){  
System.out.print(Thread.currentThread().getName());  
System.out.println(" : " + i);  
//Thread.yield(); (1)  
/* (2) */  
try{  
Thread.sleep(3000);  
}  
catch(InterruptedException e){  
System.out.println("Interrupted");  
}}}  
}  
public class TestThread{  
public static void main(String[] args){  
TestThreadMethod t1 = new TestThreadMethod("t1");  
TestThreadMethod t2 = new TestThreadMethod("t2");  
t1.setPriority(Thread.MAX_PRIORITY);  
t2.setPriority(Thread.MIN_PRIORITY);  
t1.start();  
t2.start();  
}  
} 

運行結果爲:

  1. t1 : 0  
  2. t1 : 1  
  3. t2 : 0  
  4. t1 : 2  
  5. t2 : 1  
  6. t1 : 3  
  7. t2 : 2  
  8. t2 : 3 

由結果可見,通過sleep()可使優先級較低的線程有執行的機會。註釋掉代碼(2),並去掉代碼(1)的註釋,結果爲:

  1. t1 : 0  
  2. t1 : 1  
  3. t1 : 2  
  4. t1 : 3  
  5. t2 : 0  
  6. t2 : 1  
  7. t2 : 2  
  8. t2 : 3 

可見,調用yield(),不同優先級的線程永遠不會得到執行機會。

有二種方法可以使終止線程。

1.  使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。

2.  使用interrupt方法中斷線程。

1. 使用退出標誌終止線程

當run方法執行完後,線程就會退出。但有時run方法是永遠不會結束的。如在服務端程序中使用線程進行監聽客戶端請求,或是其他的需要循環處理的任務。在這種情況下,一般是將這些任務放在一個循環中,如while循環。如果想讓循環永遠運行下去,可以使用while(true){...}來處理。但要想使while循環在某一特定條件下退出,最直接的方法就是設一個boolean類型的標誌,並通過設置這個標誌爲true或false來控制while循環是否退出。下面給出了一個利用退出標誌終止線程的例子。

  1. <SPAN style="FONT-FAMILY: SimSun">public class ThreadFlag extends Thread    
  2. {    
  3.     public volatile boolean exit = false;    
  4.    
  5.     public void run()    
  6.     {    
  7.         while (!exit);    
  8.     }    
  9.     public static void main(String[] args) throws Exception    
  10.     {    
  11.         ThreadFlag thread = new ThreadFlag();    
  12.         thread.start();    
  13.         sleep(5000); // 主線程延遲5秒     
  14.         thread.exit = true;  // 終止線程thread     
  15.         thread.join();    
  16.         System.out.println("線程退出!");    
  17.     }    
  18. }  </SPAN>  
public class ThreadFlag extends Thread  
{  
    public volatile boolean exit = false;  
 
    public void run()  
    {  
        while (!exit);  
    }  
    public static void main(String[] args) throws Exception  
    {  
        ThreadFlag thread = new ThreadFlag();  
        thread.start();  
        sleep(5000); // 主線程延遲5秒  
        thread.exit = true;  // 終止線程thread  
        thread.join();  
        System.out.println("線程退出!");  
    }  
}  

在上面代碼中定義了一個退出標誌exit,當exit爲true時,while循環退出,exit的默認值爲false。在定義exit時,使用了一個Java關鍵字volatile,這個關鍵字的目的是使exit同步,也就是說在同一時刻只能由一個線程來修改exit的值,

2. 使用interrupt方法終止線程

使用interrupt方法來終端線程可分爲兩種情況:

(1)線程處於阻塞狀態,如使用了sleep方法。

(2)使用while(!isInterrupted()){...}來判斷線程是否被中斷。

在第一種情況下使用interrupt方法,sleep方法將拋出一個InterruptedException例外,而在第二種情況下線程將直接退出。下面的代碼演示了在第一種情況下使用interrupt方法。

  1. <SPAN style="FONT-FAMILY: SimSun">public class ThreadInterrupt extends Thread    
  2. {    
  3.     public void run()    
  4.     {    
  5.         try   
  6.         {    
  7.             sleep(50000);  // 延遲50秒     
  8.         }    
  9.         catch (InterruptedException e)    
  10.         {    
  11.             System.out.println(e.getMessage());    
  12.         }    
  13.     }    
  14.     public static void main(String[] args) throws Exception    
  15.     {    
  16.         Thread thread = new ThreadInterrupt();    
  17.         thread.start();    
  18.         System.out.println("在50秒之內按任意鍵中斷線程!");    
  19.         System.in.read();    
  20.         thread.interrupt();    
  21.         thread.join();    
  22.         System.out.println("線程已經退出!");    
  23.     }    
  24. }  </SPAN>  
public class ThreadInterrupt extends Thread  
{  
    public void run()  
    {  
        try 
        {  
            sleep(50000);  // 延遲50秒  
        }  
        catch (InterruptedException e)  
        {  
            System.out.println(e.getMessage());  
        }  
    }  
    public static void main(String[] args) throws Exception  
    {  
        Thread thread = new ThreadInterrupt();  
        thread.start();  
        System.out.println("在50秒之內按任意鍵中斷線程!");  
        System.in.read();  
        thread.interrupt();  
        thread.join();  
        System.out.println("線程已經退出!");  
    }  
}  
上面代碼的運行結果如下:

在50秒之內按任意鍵中斷線程!

sleep interrupted

線程已經退出!

在調用interrupt方法後, sleep方法拋出異常,然後輸出錯誤信息:sleep interrupted。注意:在Thread類中有兩個方法可以判斷線程是否通過interrupt方法被終止。一個是靜態的方法interrupted(),一個是非靜態的方法isInterrupted(),這兩個方法的區別是interrupted用來判斷當前線是否被中斷,而isInterrupted可以用來判斷其他線程是否被中斷。因此,while (!isInterrupted())也可以換成while (!Thread.interrupted())。

                                        



線程的狀態(State)

  新生狀態(New): 當一個線程的實例被創建即使用new關鍵字和Thread類或其子類創建一個線程對象後,此時該線程處於新生(new)狀態,處於新生狀態的線程有自己的內存空間,但該線程並沒有運行,此時線程還不是活着的(not alive);

  就緒狀態(Runnable): 通過調用線程實例的start()方法來啓動線程使線程進入就緒狀態(runnable);處於就緒狀態的線程已經具備了運行條件,但還沒有被分配到CPU即不一定會被立即執行,此時處於線程就緒隊列,等待系統爲其分配CPCU,等待狀態並不是執行狀態; 此時線程是活着的(alive);

  運行狀態(Running): 一旦獲取CPU(被JVM選中),線程就進入運行(running)狀態,線程的run()方法纔開始被執行;在運行狀態的線程執行自己的run()方法中的操作,直到調用其他的方法而終止、或者等待某種資源而阻塞、或者完成任務而死亡;如果在給定的時間片內沒有執行結束,就會被系統給換下來回到線程的等待狀態;此時線程是活着的(alive);

  阻塞狀態(Blocked):通過調用join()、sleep()、wait()或者資源被暫用使線程處於阻塞(blocked)狀態;處於Blocking狀態的線程仍然是活着的(alive)

  死亡狀態(Dead):當一個線程的run()方法運行完畢或被中斷或被異常退出,該線程到達死亡(dead)狀態。此時可能仍然存在一個該Thread的實例對象,當該Thready已經不可能在被作爲一個可被獨立執行的線程對待了,線程的獨立的call stack已經被dissolved。一旦某一線程進入Dead狀態,他就再也不能進入一個獨立線程的生命週期了。對於一個處於Dead狀態的線程調用start()方法,會出現一個運行期(runtime exception)的異常;處於Dead狀態的線程不是活着的(not alive)。

 線程狀態圖

       

Ø線程的方法(Method)、屬性(Property)

1)優先級(priority)

每個類都有自己的優先級,一般property用1-10的整數表示,默認優先級是5,優先級最高是10;優先級高的線程並不一定比優先級低的線程執行的機會高,只是執行的機率高;默認一個線程的優先級和創建他的線程優先級相同;

2)Thread.sleep()/sleep(long millis)

當前線程睡眠/millis的時間(millis指定睡眠時間是其最小的不執行時間,因爲sleep(millis)休眠到達後,無法保證會被JVM立即調度);sleep()是一個靜態方法(static method) ,所以他不會停止其他的線程也處於休眠狀態;線程sleep()時不會失去擁有的對象鎖。 作用:保持對象鎖,讓出CPU,調用目的是不讓當前線程獨自霸佔該進程所獲取的CPU資源,以留一定的時間給其他線程執行的機會;

3)Thread.yield()

  讓出CPU的使用權,給其他線程執行機會、讓同等優先權的線程運行(但並不保證當前線程會被JVM再次調度、使該線程重新進入Running狀態),如果沒有同等優先權的線程,那麼yield()方法將不會起作用。

4)thread.join()

 使用該方法的線程會在此之間執行完畢後再往下繼續執行。

5)object.wait()

  當一個線程執行到wait()方法時,他就進入到一個和該對象相關的等待池(Waiting Pool)中,同時失去了對象的機鎖—暫時的,wait後還要返還對象鎖。當前線程必須擁有當前對象的鎖,如果當前線程不是此鎖的擁有者,會拋出IllegalMonitorStateException異常,所以wait()必須在synchronized block中調用。

6)object.notify()/notifyAll()

  喚醒在當前對象等待池中等待的第一個線程/所有線程。notify()/notifyAll()也必須擁有相同對象鎖,否則也會拋出IllegalMonitorStateException異常。

7)Synchronizing Block

 Synchronized Block/方法控制對類成員變量的訪問;Java中的每一個對象都有唯一的一個內置的鎖,每個Synchronized Block/方法只有持有調用該方法被鎖定對象的鎖纔可以訪問,否則所屬線程阻塞;機鎖具有獨佔性、一旦被一個Thread持有,其他的Thread就不能再擁有(不能訪問其他同步方法),方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。
 
 
【借鑑於】:
發佈了21 篇原創文章 · 獲贊 26 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章