Java多執行緒程式設計要點

醉面韋陀發表於2010-03-31

 

1、 認識Thread和Runnable

Java中實現多執行緒有兩種途徑:繼承Thread類或者實現Runnable介面。Runnable是介面,建議用介面的方式生成執行緒,因為介面可以實現多繼承,況且Runnable只有一個run方法,很適合繼承。在使用Thread的時候只需繼承Thread,並且new一個例項出來,呼叫 start()方法即可以啟動一個執行緒。

Thread Test = new Thread();

Test.start();

在使用Runnable的時候需要先new一個實現Runnable的例項,之後啟動Thread即可。

Test impelements Runnable;

Test t = new Test();

Thread test = new Thread(t);

test.start();

總結:Thread和Runnable是實現java多執行緒的2種方式,runable是介面,thread是類,建議使用runable實現 java多執行緒,不管如何,最終都需要通過thread.start()來使執行緒處於可執行狀態。

2、 認識Thread的start和run

1) start:

用start方法來啟動執行緒,真正實現了多執行緒執行,這時無需等待run方法體程式碼執行完畢而直接繼續執行下面的程式碼。通過呼叫Thread類的 start()方法來啟動一個執行緒,這時此執行緒處於就緒(可執行)狀態,並沒有執行,一旦得到cpu時間片,就開始執行run()方法,這裡方法 run()稱為執行緒體,它包含了要執行的這個執行緒的內容,Run方法執行結束,此執行緒隨即終止。

2) run:

run()方法只是類的一個普通方法而已,如果直接呼叫Run方法,程式中依然只有主執行緒這一個執行緒,其程式執行路徑還是隻有一條,還是要順序執行,還是要等待run方法體執行完畢後才可繼續執行下面的程式碼,這樣就沒有達到寫執行緒的目的。

總結:呼叫start方法方可啟動執行緒,而run方法只是thread的一個普通方法呼叫,還是在主執行緒裡執行。

3、 執行緒狀態說明

執行緒狀態從大的方面來說,可歸結為:初始狀態、可執行狀態、不可執行狀態和消亡狀態,具體可細分為上圖所示7個狀態,說明如下:

1) 執行緒的實現有兩種方式,一是繼承Thread類,二是實現Runnable介面,但不管怎樣,當我們new了thread例項後,執行緒就進入了初始狀態;

2) 當該物件呼叫了start()方法,就進入可執行狀態;

3) 進入可執行狀態後,當該物件被作業系統選中,獲得CPU時間片就會進入執行狀態;

4) 進入執行狀態後case就比較多,大致有如下情形:

﹒run()方法或main()方法結束後,執行緒就進入終止狀態;

﹒當執行緒呼叫了自身的sleep()方法或其他執行緒的join()方法,就會進入阻塞狀態(該狀態既停止當前執行緒,但並不釋放所佔有的資源)。當 sleep()結束或join()結束後,該執行緒進入可執行狀態,繼續等待OS分配時間片;

﹒當執行緒剛進入可執行狀態(注意,還沒執行),發現將要呼叫的資源被鎖牢(synchroniza,lock),將會立即進入鎖池狀態,等待獲取鎖標記(這時的鎖池裡也許已經有了其他執行緒在等待獲取鎖標記,這時它們處於佇列狀態,既先到先得),一旦執行緒獲得鎖標記後,就轉入可執行狀態,等待OS分配 CPU時間片;

﹒當執行緒呼叫wait()方法後會進入等待佇列(進入這個狀態會釋放所佔有的所有資源,與阻塞狀態不同),進入這個狀態後,是不能自動喚醒的,必須依靠其他執行緒呼叫notify()或notifyAll()方法才能被喚醒(由於notify()只是喚醒一個執行緒,但我們由不能確定具體喚醒的是哪一個執行緒,也許我們需要喚醒的執行緒不能夠被喚醒,因此在實際使用時,一般都用notifyAll()方法,喚醒有所執行緒),執行緒被喚醒後會進入鎖池,等待獲取鎖標記。

﹒當執行緒呼叫stop方法,即可使執行緒進入消亡狀態,但是由於stop方法是不安全的,不鼓勵使用,大家可以通過run方法裡的條件變通實現執行緒的 stop。

4、 Timer 和 Timer Task 的使用


Timer 是一種定時器工具,用來在一個後臺執行緒計劃執行指定任務,這些任務可以被執行一次,也可以被定期執行。每個 Timer 物件對應一個後臺執行緒,順序地執行所有計時器任務。如果完成某個計時器任務的時間太長,那麼它會“獨佔”計時器的任務執行執行緒,從而可能延遲後續任務的執行。對 Timer 物件最後的引用完成並且所有未處理的任務都已執行完成後,計時器的任務執行執行緒會正常終止(並且成為垃圾回收的物件)。TimerTask是一個抽象類,實現了Runable介面,它的子類代表一個可以被Timer計劃的任務。

1) 一個簡單的Demo,讓大家對Timer、TimerTask的使用有感性的認識。



2) Timer和TimerTask的常用api函式說明



這裡強調Timer類的schedule和scheduleAtFixedRate的區別。schedule和 scheduleAtFixedRate的區別在於,schedule以固定的相對時間間隔執行,如果某一次執行被延時了,往後的執行的執行時間也會相對延時;而scheduleAtFixedRate是以絕對的時間間隔執行,如果某一次執行被延時,它的後一次執行的延時將會縮短(scheduleAtFixedRate會把已經過去的時間也作為週期執行)。schedule注重的是時間間隔的穩定,而 scheduleAtFixedRate注重的是執行頻率的穩定。

3) Timer的終止

預設情況下,只要一個程式的timer執行緒在執行,那麼這個程式就會保持執行。當然,你可以通過以下四種方法終止一個timer執行緒:

a)呼叫timer的cancle方法。你可以從程式的任何地方呼叫此方法,甚至在一個timer task的run方法裡;

b)讓timer執行緒成為一個daemon執行緒(可以在建立timer時使用new Timer(true)達到這個目地),這樣當程式只有daemon執行緒的時候,它就會自動終止執行;

c)當timer相關的所有task執行完畢以後,刪除所有此timer物件的引用(置成null),這樣timer執行緒也會終止;

d)呼叫System.exit方法,使整個程式(所有執行緒)終止。

總結:Timer和TimerTask可以簡單理解為Timer定時器在觸發TimerTask任務呼叫,通常用schedule和 scheduleAtFixedRate方法來呼叫timertask任務,cancle來終止任務呼叫。Timer簡單易用,比較適合提供輕量級的計時器功能,但是對時效性很強的任務排程請用其它方法來實現(正如javadoc所述”Timer does not offer real-time guarantees: it schedules tasks using the Object.wait(long) method”)。

4、 callable 實現多執行緒

  1. import java.util.concurrent.Callable;   
  2. import java.util.concurrent.ExecutorService;   
  3. import java.util.concurrent.Executors;   
  4. import java.util.concurrent.Future;   
  5.   
  6. /** *//**  
  7.  * Callable 和 Future介面  
  8.  * Callable是類似於Runnable的介面,實現Callable介面的類和實現Runnable的類都是可被其它執行緒執行的任務。  
  9.  * Callable和Runnable有幾點不同:  
  10.  * (1)Callable規定的方法是call(),而Runnable規定的方法是run().  
  11.  * (2)Callable的任務執行後可返回值,而Runnable的任務是不能返回值的。  
  12.  * (3)call()方法可丟擲異常,而run()方法是不能丟擲異常的。  
  13.  * (4)執行Callable任務可拿到一個Future物件,  
  14.  * Future 表示非同步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。  
  15.  * 通過Future物件可瞭解任務執行情況,可取消任務的執行,還可獲取任務執行的結果。  
  16.  */  
  17. public class CallableAndFuture {   
  18.   
  19.     /** *//**  
  20.      * 自定義一個任務類,實現Callable介面  
  21.      */  
  22.     public static class MyCallableClass implements Callable{   
  23.         // 標誌位   
  24.         private int flag = 0;   
  25.         public MyCallableClass(int flag){   
  26.             this.flag = flag;   
  27.         }   
  28.         public String call() throws Exception{   
  29.             if (this.flag == 0){   
  30.                 // 如果flag的值為0,則立即返回   
  31.                 return "flag = 0";   
  32.             }    
  33.             if (this.flag == 1){   
  34.                 // 如果flag的值為1,做一個無限迴圈   
  35.                 try {   
  36.                     while (true) {   
  37.                         System.out.println("looping.");   
  38.                         Thread.sleep(2000);   
  39.                     }   
  40.                 } catch (InterruptedException e) {   
  41.                     System.out.println("Interrupted");   
  42.                 }   
  43.                 return "false";   
  44.             } else {   
  45.                 // falg不為0或者1,則丟擲異常   
  46.                 throw new Exception("Bad flag value!");   
  47.             }   
  48.         }   
  49.     }   
  50.        
  51.     public static void main(String[] args) {   
  52.         // 定義3個Callable型別的任務   
  53.         MyCallableClass task1 = new MyCallableClass(0);   
  54.         MyCallableClass task2 = new MyCallableClass(1);   
  55.         MyCallableClass task3 = new MyCallableClass(2);   
  56.            
  57.         // 建立一個執行任務的服務   
  58.         ExecutorService es = Executors.newFixedThreadPool(3);   
  59.         try {   
  60.             // 提交併執行任務,任務啟動時返回了一個 Future物件,   
  61.             // 如果想得到任務執行的結果或者是異常可對這個Future物件進行操作   
  62.             Future future1 = es.submit(task1);   
  63.             // 獲得第一個任務的結果,如果呼叫get方法,當前執行緒會等待任務執行完畢後才往下執行   
  64.             System.out.println("task1: " + future1.get());   
  65.                
  66.             Future future2 = es.submit(task2);   
  67.             // 等待5秒後,再停止第二個任務。因為第二個任務進行的是無限迴圈   
  68.             Thread.sleep(5000);   
  69.             System.out.println("task2 cancel: " + future2.cancel(true));   
  70.                
  71.             // 獲取第三個任務的輸出,因為執行第三個任務會引起異常   
  72.             // 所以下面的語句將引起異常的丟擲   
  73.             Future future3 = es.submit(task3);   
  74.             System.out.println("task3: " + future3.get());   
  75.         } catch (Exception e){   
  76.             System.out.println(e.toString());   
  77.         }   
  78.         // 停止任務執行服務   
  79.         es.shutdownNow();   
  80.     }   
  81. }  

相關文章