天天看點

java中建立線程的三種方法以及差別

文章目錄

  • ​​概述​​
  • ​​繼承Thread類建立線程​​
  • ​​實作Runnable接口建立線程​​
  • ​​使用Callable和Future建立線程​​
  • ​​三種建立線程方法對比​​

概述

Java使用Thread類代表線程,所有的線程對象都必須是Thread類或其子類的執行個體。Java可以用三種方式來建立線程,如下所示:

  1. 繼承Thread類建立線程
  2. 實作Runnable接口建立線程
  3. 使用Callable和Future建立線程

下面讓我們分别來看看這三種建立線程的方法。

繼承Thread類建立線程

通過繼承Thread類來建立并啟動多線程的一般步驟如下

  1. d定義Thread類的子類,并重寫該類的run()方法,該方法的方法體就是線程需要完成的任務,run()方法也稱為線程執行體。
  2. 建立Thread子類的執行個體,也就是建立了線程對象
  3. 啟動線程,即調用線程的start()方法

代碼執行個體

public class MyThread extends Thread{//繼承Thread類

  public void run(){

  //重寫run方法

  }
}

public class Main {

  public static void main(String[] args){

    new MyThread().start();//建立并啟動線程

  }
}      

實作Runnable接口建立線程

通過實作Runnable接口建立并啟動線程一般步驟如下:

  1. 定義Runnable接口的實作類,一樣要重寫run()方法,這個run()方法和Thread中的run()方法一樣是線程的執行體
  2. 建立Runnable實作類的執行個體,并用這個執行個體作為Thread的target來建立Thread對象,這個Thread對象才是真正的線程對象
  3. 第三部依然是通過調用線程對象的start()方法來啟動線程

代碼執行個體:

public class MyThread2 implements Runnable {//實作Runnable接口
  public void run(){
  //重寫run方法
  }
}

public class Main {
  public static void main(String[] args){
    //建立并啟動線程
    MyThread2 myThread=new MyThread2();
    Thread thread=new Thread(myThread);
    thread().start();
    //或者    new Thread(new MyThread2()).start();
  }
}      

使用Callable和Future建立線程

和Runnable接口不一樣,Callable接口提供了一個call()方法作為線程執行體,call()方法比run()方法功能要強大。

  1. call()方法可以有傳回值
  2. call()方法可以聲明抛出異常

Java5提供了Future接口來代表Callable接口裡call()方法的傳回值,并且為Future接口提供了一個實作類FutureTask,這個實作類既實作了Future接口,還實作了Runnable接口,是以可以作為Thread類的target。在Future接口裡定義了幾個公共方法來控制它關聯的Callable任務。

boolean cancel(boolean mayInterruptIfRunning):視圖取消該Future裡面關聯的Callable任務

V get():傳回Callable裡call()方法的傳回值,調用這個方法會導緻程式阻塞,必須等到子線程結束後才會得到傳回值

V get(long timeout,TimeUnit unit):傳回Callable裡call()方法的傳回值,最多阻塞timeout時間,經過指定時間沒有傳回抛出TimeoutException

boolean isDone():若Callable任務完成,傳回True

boolean isCancelled():如果在Callable任務正常完成前被取消,傳回True

介紹了相關的概念之後,建立并啟動有傳回值的線程的步驟如下:

  1. 建立Callable接口的實作類,并實作call()方法,然後建立該實作類的執行個體(從java8開始可以直接使用Lambda表達式建立Callable對象)。
  2. 使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了Callable對象的call()方法的傳回值
  3. 使用FutureTask對象作為Thread對象的target建立并啟動線程(因為FutureTask實作了Runnable接口)
  4. 調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值

代碼執行個體:

public class Main {
  public static void main(String[] args){
   MyThread3 th=new MyThread3();
   //使用Lambda表達式建立Callable對象
     //使用FutureTask類來包裝Callable對象
   FutureTask<Integer> future=new FutureTask<Integer>(
    (Callable<Integer>)()->{
      return 5;
    }
    );
   new Thread(task,"有傳回值的線程").start();//實質上還是以Callable對象來建立并啟動線程
    try{
    System.out.println("子線程的傳回值:"+future.get());//get()方法會阻塞,直到子線程執行結束才傳回
    }catch(Exception e){
    ex.printStackTrace();
   }
}      

三種建立線程方法對比

  1. 線程隻是實作Runnable或實作Callable接口,還可以繼承其他類。
  2. 這種方式下,多個線程可以共享一個target對象,非常适合多線程處理同一份資源的情形。
  3. 但是程式設計稍微複雜,如果需要通路目前線程,必須調用Thread.currentThread()方法。
  4. 繼承Thread類的線程類不能再繼承其他父類(Java單繼承決定)。