天天看點

ThreadPoolExecutor的中的submit和FutureTask || 通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現)前言:一、ThreadPoolExecutor的中的submit和FutureTask 二、通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現) 三、總結

目錄

前言:

一、ThreadPoolExecutor的中的submit和FutureTask 

二、通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現)

Runnable任務類型和Callable任務類型

Runnable接口、Callable接口建立線程

Runable和Callable任務類型的差別:

線程池的一些補充

 三、總結

線程的建立

Runnable和Callable 

前言:

ThreadPoolExecutor 的 void execute(Runnable command) 方法,利用這個方法雖然可以送出任務,但是卻沒有辦法擷取任務的執行結果(execute() 方法沒有傳回值)

而很多場景下,我們又都是需要擷取任務的執行結果的。那 ThreadPoolExecutor 是否提供了相關功能呢?必須的,這麼重要的功能當然需要提供了——那就是sumbit

ThreadPoolExecutor的中的submit和FutureTask || 通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現)前言:一、ThreadPoolExecutor的中的submit和FutureTask 二、通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現) 三、總結
  • execute隻能送出Runnable類型的任務,無傳回值。submit既可以送出Runnable類型的任務,也可以送出Callable類型的任務,會有一個類型為Future的傳回值,但當任務類型為Runnable時,傳回值為null。
  • execute在執行任務時,如果遇到異常會直接抛出,而submit不會直接抛出,隻有在使用Future的get方法擷取傳回值時,才會抛出異常。

一、ThreadPoolExecutor的中的submit和FutureTask 

 Executors 本質上是 ThreadPoolExecutor 類的封裝.

Executors類和ThreadPoolExecutor都是util.concurrent并發包下面的類, Executos下面的newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor、newCachedThreadPool底線的實作都是用的ThreadPoolExecutor實作的,所有ThreadPoolExecutor更加靈活。

Java 通過 ThreadPoolExecutor 提供的 3 個 submit() 方法和 1 個 FutureTask 工具類來支援獲得任務執行結果的需求。下面我們先來介紹這 3 個 submit() 方法,這 3 個方法的方法簽名如下。

// 送出 Runnable 任務
Future<?>
    submit(Runnable task);

// 送出 Callable 任務
<T> Future<T>
    submit(Callable<T> task);

// 提文 Runnable 任務及結果引用
<T> Future<T>
    submit(Runnable task T result);
           

你會發現它們的傳回值都是 Future 接口

Future 接口有 5 個方法

取消任務的方法 cancel()、判斷任務是否已取消的方法 isCancelled()、判斷任務是否已結束的方法 isDone()以及2 個獲得任務執行結果的 get() 和 get(timeout, unit)

其中最後一個 get(timeout, unit) 支援逾時機制。通過 Future 接口的這 5 個方法你會發現,我們送出的任務不但能夠擷取任務執行結果,還可以取消任務。不過需要注意的是:這兩個 get() 方法都是阻塞式的,如果被調用的時候,任務還沒有執行完,那麼調用 get() 方法的線程會阻塞,直到任務執行完才會被喚醒。 

這 3 個 submit() 方法之間的差別在于方法參數不同,下面我們簡要介紹一下。

  1. 送出 Runnable 任務 

    submit(Runnable task)

     :這個方法的參數是一個 Runnable 接口,Runnable 接口的 run() 方法是沒有傳回值的,是以 

    submit(Runnable task)

     這個方法傳回的 Future 僅可以用來斷言任務已經結束了,類似于 Thread.join()。
  2. 送出 Callable 任務 

    submit(Callable<T> task)

    :這個方法的參數是一個 Callable 接口,它隻有一個 call() 方法,并且這個方法是有傳回值的,是以這個方法傳回的 Future 對象可以通過調用其 get() 方法來擷取任務的執行結果。
  3. 送出 Runnable 任務及結果引用 

    submit(Runnable task, T result)

    :這個方法很有意思,假設這個方法傳回的 Future 對象是 f,f.get() 的傳回值就是傳給 submit() 方法的參數 result。這個方法該怎麼用呢?下面這段示例代碼展示了它的經典用法。需要你注意的是 Runnable 接口的實作類 Task 聲明了一個有參構造函數 

    Task(Result r)

     ,建立 Task 對象的時候傳入了 result 對象,這樣就能在類 Task 的 run() 方法中對 result 進行各種操作了。result 相當于主線程和子線程之間的橋梁,通過它主子線程可以共享資料。

那麼既然Executor是對ThreadPoolExecutor的封裝,那麼通過Executor建立的線程池自然也同樣有上述3個submit方法和1個FutureTask工具類。

二、通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現)

package Thread;

/**
 * 用Callable和FutureTask建立線程
 */

import java.util.concurrent.*;
import java.util.concurrent.ExecutorService;

public class CallableFutureTask {
    public static void main(String[] args) {
        //第一種方式
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        executor.submit(futureTask);
        executor.shutdown();

        //第二種方式,注意這種方式和第一種方式效果是類似的,隻不過一個使用的是ExecutorService,一個使用的是Thread
        /*Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        Thread thread = new Thread(futureTask);
        thread.start();*/

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        System.out.println("主線程在執行任務");

        try {
            System.out.println("task運作結果"+futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("所有任務執行完畢");
    }
}
class Task implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("子線程在進行計算");
        Thread.sleep(3000);
        int sum = 0;
        for(int i=0;i<100;i++)
            sum += i;
        return sum;
    }
}
           

Runnable任務類型和Callable任務類型

Runnable接口、Callable接口建立線程

首先我們要知道可以通過以下兩種建立線程

  • 實作Runable接口,重寫run方法。
  • 使用Callable接口 和 Future接口建立線程。(線程處于并發狀态, 預設異步執行)

實作Runable接口,重寫run方法。

package Thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 線程建立——》實作Runnable接口,重寫run方法
 */
class MyRunable implements Runnable {
    @Override
    public void run() {
        System.out.println("實作Runable接口,重寫run方法");
    }
}
public class thread3 {
// 使用了線程池
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        MyRunable myRunable = new MyRunable();
        es.submit(myRunable); // 将我們的Runnable任務送出到我們線程池中
        es.shutdown();
//        FutureTask:是對Runnable和Callable的進一步封裝,
//        相比直接把Runnable和Callable扔給線程池,FutureTask的功能更多
    }
// 未使用線程池(隻是Thread)
    public static void main1(String[] args) {
        MyRunable myRunable = new MyRunable();
        Thread thread = new Thread(myRunable);
        // Thread thread1 = new Thread(new MyRunable());
        thread.start();
    }
}
           

使用Callable和Future建立線程 

和Runnable接口不一樣,Callable接口提供了一個call()方法作為線程執行體,call()方法比run()方法功能要強大:call()方法可以有傳回值,可以聲明抛出異常。 

public interface Callable<V> {
    V call() throws Exception;
}
           

Java5提供了Future接口來接收Callable接口中call()方法的傳回值。

Callable接口是 Java5 新增的接口,不是Runnable接口的子接口,是以Callable對象不能直接作為Thread對象的target。

針對這個問題,引入了RunnableFuture接口,RunnableFuture接口是Runnable接口和Future接口的子接口,可以作為Thread對象的target 。同時,Java5提供了一個RunnableFuture接口的實作類:FutureTask ,FutureTask可以作為 Thread對象的target

ThreadPoolExecutor的中的submit和FutureTask || 通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現)前言:一、ThreadPoolExecutor的中的submit和FutureTask 二、通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現) 三、總結

 一個案例

package Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
/**
 * 和Runnable接口不一樣,Callable接口提供了一個call()方法作為線程執行體,call()方法比run()方法功能要強大。
 *  同時建立對象的時候,
 * 》call()方法可以有傳回值
 *
 * 》call()方法可以聲明抛出異常
 * Java5提供了Future接口來代表Callable接口裡call()方法的傳回值,并且為Future接口提供了一個實作類FutureTask,
 * 這個實作類既實作了Future接口,還實作了Runnable接口,是以可以作為Thread類的target。在Future接口裡定義了幾個公共方法來控制它關聯的Callable任務。
 */
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("這是用Callable建立線程的一個嘗試!");
        return "xixi";
    }
}
public class thread1 {
// 隻是用來Thread
    public static void main1(String[] args) throws ExecutionException, InterruptedException {
        // 1】建立Callable接口的實作類,并實作call()方法,然後建立該實作類的執行個體(從java8開始可以直接使用Lambda表達式建立Callable對象)。
        MyCallable myThread = new MyCallable(); // myThread是一個Callable對象

        //2】使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了Callable對象的call()方法的傳回值
        FutureTask<String> futureTask = new FutureTask<String>(myThread); // 與Callable關聯

        //3】使用FutureTask對象作為Thread對象的target建立并啟動線程(因為FutureTask實作了Runnable接口)——實質上還是以Callable對象來建立并啟動線程
        // FutureTask實作Future接口,說明可以從FutureTask中通過get取到任務的傳回結果,也可以取消任務執行(通過interreput中斷)
        Thread thread = new Thread(futureTask, "有傳回值的線程");
         thread.start();
        // 4】調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值
        System.out.println("子線程的傳回值" + futureTask.get()); //get()方法會阻塞,直到子線程執行結束才傳回
    }
    // 使用了線程池
    public static void main2(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        MyCallable myCallable = new MyCallable();
        es.submit(myCallable); // 你直接把Callable任務丢給線程池,擷取不到call傳回值
        es.shutdown();
        // FutureTask:是對Runnable和Callable的進一步封裝,
        //相比直接把Runnable和Callable扔給線程池,FutureTask的功能更多
    }
    // 使用了線程池
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();
        MyCallable myCallable = new MyCallable();
        //2】使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了Callable對象的call()方法的傳回值
        FutureTask<String> futureTask = new FutureTask<String>(myCallable); // 與Callable關聯
        es.submit(futureTask); // 你直接把Callable任務丢給線程池,擷取不到call傳回值
        System.out.println(futureTask.get()); // 通過futureTask列印傳回值
        es.shutdown();
    }


}
           

使用Callable和Future建立線程的 總結

使用Callable和Future建立線程的步驟如下:(未使用線程池)

(1)定義一個類實作Callable接口,并重寫call()方法,該call()方法将作為線程執行體,并且有傳回值

(2)建立Callable實作類的執行個體,使用FutureTask類來包裝Callable對象

(3)使用FutureTask對象作為Thread對象的target建立并啟動線程

(4)調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值

使用Callable和Future建立線程的步驟如下:(使用線程池)

(1)定義一個類實作Callable接口,并重寫call()方法,該call()方法将作為線程執行體,并且有傳回值

(2)建立Callable實作類的執行個體,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了Callable對象的call()方法的傳回值

(3)建立線程池

(4)通過sumbit()把封裝了Callable對象的futureTask送出到線程池中

(5)調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值

ThreadPoolExecutor的中的submit和FutureTask || 通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現)前言:一、ThreadPoolExecutor的中的submit和FutureTask 二、通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現) 三、總結

當然如果你用了線程池,你也可以直接送出把你執行個體化的Callable對象和Runnable對象送出線程池中(不用FutureTask封裝)

ThreadPoolExecutor的中的submit和FutureTask || 通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現)前言:一、ThreadPoolExecutor的中的submit和FutureTask 二、通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現) 三、總結

但是這裡,你直接把Callable任務丢給線程池,你擷取不到call方法傳回值

// FutureTask:是對Runnable和Callable的進一步封裝,
//相比直接把Runnable和Callable扔給線程池,FutureTask的功能更多      

Runable和Callable任務類型的差別:

兩者都可以被ExecutorService執行

  • Callable的call()方法隻能通過ExecutorService的 submit(Callable task) 方法來執行,并且傳回一個 Future,是表示任務等待完成的 Future.
  • Runnable的run方法,無傳回值,無法抛出經過檢查的異常。Callable的call方法,有傳回值V,并且可能抛出異常。
  • 将Callable的對象傳遞給ExecutorService的submit方法,則該call方法自動在一個線程上執行,
  • 并且會傳回執行結果Future對象。
  • 将Runnable的對象傳遞給ExecutorService的submit方法,則該run方法自動在一個線程上執行,
  • 并且會傳回執行結果Future對象,但是在該Future對象上調用的方法傳回的是null.

線程池的一些補充

Runnable:

可以直接用execute或sumbit送出到線程池

Callable:

功能相比Runnable來說少很多,不能用來建立線程(要和Future接口一起),也不能直接扔給線程池的execute方法。但是其中的call方法有傳回值

FutureTask 實作了 Runnable 和 Future 接口,由于實作了 Runnable 接口,是以可以将 FutureTask 對象作為任務送出給 ThreadPoolExecutor 去執行,也可以直接被 Thread 執行;又因為實作了 Future 接口,是以也能用來獲得任務的執行結果

ThreadPoolExecutor的中的submit和FutureTask || 通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現)前言:一、ThreadPoolExecutor的中的submit和FutureTask 二、通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現) 三、總結

 三、總結

線程的建立

線程的建立有4種方法

1、通過繼承Thread類,重寫其中的run方法。

2、通過實作Runnable接口,重寫其中的run方法。

我們的Runnable任務可以直接作為new Thread中的target。

3、通過實作Callable類,重寫其中的call方法

但是此時你得到的MyCallable執行個體(callable任務)不能直接作為new Thread()中的target,放到括号中。你需要通過FutureTask包裝一下你的MyCallable,得到futureTask因為FutureTask實作了Runnable接口,是以futureTask可以作為Thread類的Target——》new Thread(futureTask)

上面我們說的是沒有用到線程池的情況下。

如果使用了線程池,線程池的sumbit可以送出Runnable任務和Callable任務。但是execute隻能送出Runnable任務。

//        FutureTask:是對Runnable和Callable的進一步封裝,
//        相比直接把Runnable和Callable扔給線程池,FutureTask的功能更多      

Runnable和Callable 

1.實作Runnable/Callable接口相比繼承Thread類的優勢

(1)适合多個線程進行資源共享

(2)可以避免java中單繼承的限制

(3)增加程式的健壯性,代碼和資料獨立

(4)線程池隻能放入Runable或Callable接口實作類,不能直接放入繼承Thread的類

2.Callable和Runnable的差別

(1) Callable重寫的是call()方法,Runnable重寫的方法是run()方法

(2) call()方法執行後可以有傳回值,run()方法沒有傳回值

(3) call()方法可以抛出異常,run()方法不可以

(4) 運作Callable任務可以拿到一個Future對象,表示異步計算的結果 。通過Future對象可以了解任務執行情況,可取消任務的執行,還可擷取執行結果