1. 程式人生 > >Java建立執行緒的三種方式以及優劣對比

Java建立執行緒的三種方式以及優劣對比

Java使用Thread類代表執行緒,所有的執行緒物件都必須是Thread類或者其子類例項。每個執行緒的作用是完成一定的任務,實際上是執行一段程式流

#1. 繼承Thread類建立執行緒類     通過繼承Thread類來建立並啟動多執行緒的步驟如下:

  1. 定義Thread類的子類,並重寫該類的run()方法。
  2. 建立子類的例項,即建立了執行緒物件。
  3. 呼叫執行緒物件的start()方法來啟動該執行緒。

    考慮下面這個示例:

public class ThreadTest extends Thread{
    //重寫run()方法,run()方法的方法體是執行緒執行體
    public
void run(){ for(int i=0;i<5;i++){ //使用執行緒的getName()方法可以直接獲取當前執行緒的名稱 System.out.println(this.getName() + " " + i); } } public static void main(String[] args){ //輸出Java程式執行時預設執行的主執行緒名稱 System.out.println(Thread.currentThread().getName(
)); //建立第一個執行緒並開始執行 new ThreadTest().start(); //建立第二個執行緒並開始執行 new ThreadTest().start(); } }

    輸出的示例如下: 執行緒示例程式執行結果     因為多執行緒程式在執行的時候,多個執行緒在一個CPU上快速輪換執行,所以可能得到的輸出結果並不相同。

    注意:

  1. Java程式執行時有預設的主執行緒,它的方法體就是main()方法的方法體。

  2. 使用繼承Thread類的方法來建立執行緒類時,多個執行緒之間無法共享執行緒類的例項變數。 #2.實現Runnable介面來建立執行緒類     實現Runnable介面來建立並啟動多執行緒的步驟如下:

  3. 定義Runnable介面的實現類,並重寫run()方法。

  4. 建立Runnable實現類的例項,並將它作為Target傳入Thread類的構造器以建立Thread物件。

  5. 呼叫執行緒物件的start()方法來啟動執行緒。

    考慮下面這個示例:

public class ThreadTest implements Runnable{
    private int i;
    //重寫run()方法,run()方法的方法體是執行緒執行體
    public void run(){
        //實現Runnable介面時,只能使用如下方法獲取執行緒名
        System.out.println(Thread.currentThread().getName() + "   " + i);
        i++;
    }
 
    public static void main(String[] args){
        ThreadTest tt = new ThreadTest();
        //建立第一個執行緒並開始執行
        //輸出   新執行緒1   0
        new Thread(tt,"新執行緒1").start();
        //建立第二個執行緒並開始執行
        //輸出   新執行緒2   1
        new Thread(tt,"新執行緒2").start();
        //使用Lambda表示式建立Runnable物件
        new Thread(()->{
            System.out.print("AmosH");
            System.out.println("'s blog");
        }).start();
    }
}

    這兩個建立執行緒類的方法之間的區別是:前者直接建立的Thread子類即可代表執行緒物件;後者建立的Runnable物件只能作為執行緒物件的target。

    注意:

    使用這種方法,程式建立的Runnable物件只是執行緒的target,而多個執行緒可以共享同一個target,所以多個執行緒可以共享同一個執行緒類的例項變數。 #3.使用Callable和Future建立執行緒     當使用實現Runnable介面以建立執行緒時,我們只能把run()方法包裝成為執行緒執行體,那麼是否可以把任意方法都包裝成執行緒執行體呢?

    答案是不行。但是從Java5開始,Java提供了Callable介面,該介面提供了一個可以作為執行緒執行體的call()方法,同時該方法可以有返回值而且可以丟擲錯誤。

    但是Callable介面不能直接作為Thread的target來使用,Java 5中提供了Future介面來代表Callable介面中的call()方法的返回值,並且為Future介面提供了一個FutureTask實現類,該實現類實現了Future介面,並實現了Runnable介面——可以作為Thread類的target。

    同時在Future介面中還定義瞭如下幾個公共方法用來控制和它關聯的Callable任務。

  1. boolean cancel(boolean mayInterruptIfRunning):試圖取消該Future關聯的Callable任務。
  2. V get():獲取關聯的任務的返回值。呼叫該方法將導致程式阻塞,必須等到子執行緒結束後才會得到返回值。
  3. V get(long timeout,TimeUnit unit):獲取關聯的任務的返回值。該方法最多讓程式阻塞timeout和unit指定時間,如果經過指定時間後Callable任務仍然沒有返回值,則將丟擲TimeoutException異常。
  4. boolean isCancelled():如果在Callable任務完成之前被取消,則返回true
  5. boolean isDone():如果任務已經完成,則返回true

    Callable介面有泛型限制,介面中的泛型形參型別必須和call()方法返回值型別相同。而且Callable介面是函式式介面,因此可以使用Lambda表示式建立Callable物件。

    使用該方法建立並啟動執行緒的步驟類似於實現Runnable介面來建立執行緒,考慮下面這個例子:

import java.util.concurrent.FutureTask;
 
public class ThreadTest {
    public static void main(String[] args){
        //使用FutureTask來包裝Callable物件
        //使用Lambda表示式來建立Callable<Integer>物件
        FutureTask<Integer> task = new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName() + "   " + "開始執行任務!");
            return 0;
        });
        //實質還是以Callable物件來建立並啟動執行緒
        //輸出 新執行緒   開始執行任務!
        new Thread(task,"新執行緒").start();
        try{
            //獲取執行緒的返回值
            //輸出 0
            System.out.print(task.get());
        }catch (Exception ex){
            ex.printStackTrace();
        }
 
    }
}

#建立執行緒的三種方式的對比: 採用Runnable和Callable介面的方式建立多執行緒的優缺點:

  • 執行緒類只實現了Runnable介面或者Callable介面,還可以繼承其他類。

  • 多個執行緒可以共享一個target物件,非常適合多個相同執行緒來處理同一份資源的情況,從而可以將CPU、程式碼和資料分開,形成清晰的模型,體現了面向物件的程式設計思想。

  • 程式設計稍微複雜些。 採用繼承Thread類的方式建立多執行緒的優缺點:

  • 執行緒類已經繼承了Thread類,不能再繼承其他類。

  • 程式設計比較簡單。

綜上,一般推薦使用Runnable和Callable介面的方式建立多執行緒.