目錄
前言:
一、ThreadPoolExecutor的中的submit和FutureTask
二、通過Executors 建立線程池的一些執行個體(Callable和Runnable的在其中的展現)
Runnable任務類型和Callable任務類型
Runnable接口、Callable接口建立線程
Runable和Callable任務類型的差別:
線程池的一些補充
三、總結
線程的建立
Runnable和Callable
前言:
ThreadPoolExecutor 的 void execute(Runnable command) 方法,利用這個方法雖然可以送出任務,但是卻沒有辦法擷取任務的執行結果(execute() 方法沒有傳回值)
而很多場景下,我們又都是需要擷取任務的執行結果的。那 ThreadPoolExecutor 是否提供了相關功能呢?必須的,這麼重要的功能當然需要提供了——那就是sumbit
- 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() 方法之間的差別在于方法參數不同,下面我們簡要介紹一下。
- 送出 Runnable 任務
:這個方法的參數是一個 Runnable 接口,Runnable 接口的 run() 方法是沒有傳回值的,是以
submit(Runnable task)
這個方法傳回的 Future 僅可以用來斷言任務已經結束了,類似于 Thread.join()。
submit(Runnable task)
- 送出 Callable 任務
:這個方法的參數是一個 Callable 接口,它隻有一個 call() 方法,并且這個方法是有傳回值的,是以這個方法傳回的 Future 對象可以通過調用其 get() 方法來擷取任務的執行結果。
submit(Callable<T> task)
- 送出 Runnable 任務及結果引用
:這個方法很有意思,假設這個方法傳回的 Future 對象是 f,f.get() 的傳回值就是傳給 submit() 方法的參數 result。這個方法該怎麼用呢?下面這段示例代碼展示了它的經典用法。需要你注意的是 Runnable 接口的實作類 Task 聲明了一個有參構造函數
submit(Runnable task, T result)
,建立 Task 對象的時候傳入了 result 對象,這樣就能在類 Task 的 run() 方法中對 result 進行各種操作了。result 相當于主線程和子線程之間的橋梁,通過它主子線程可以共享資料。
Task(Result r)
那麼既然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
一個案例
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()方法來獲得子線程執行結束後的傳回值
當然如果你用了線程池,你也可以直接送出把你執行個體化的Callable對象和Runnable對象送出線程池中(不用FutureTask封裝)
但是這裡,你直接把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 接口,是以也能用來獲得任務的執行結果
三、總結
線程的建立
線程的建立有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對象可以了解任務執行情況,可取消任務的執行,還可擷取執行結果