# 牛年自強計畫 Week 4 - SpringBoot - 番外篇 Java 執行緒 Future
## 【前言】
前面依序介紹過了 Thread、Thread Pool、和同步/非同步後,接下來講講在執行 Thread 時或之後,若 Thread 本身是有提供結果的話,如何取得結果這件事情。
在本篇我們會講到 Future 這個類的介紹以及應用,當然不一定只能透過 Future 去取得 Thread 的結果,額外還會介紹幾種實務面上的做法供大家參考。
## 【取得異步任務結果的方法】
依據不同業務邏輯,有針對不同方式的處理法
- 直接 Print 出 / Export File
> 僅需文字訊息的話,但也有搭配下列情況的時候,例如 Print 出過程的 Log 等。或是直接在流程中把結果進行檔案輸出
- 存入 DB,後續取出使用
> 可能使用的情境為需要慎重更新 DB 的資料時,可搭配 Transaction 使用
- 存入 Static 物件中
> 可能為一個會迅速變動的值,不斷透過 Threads 做更動,或是把結果放入集合中等待處理
- 放入外部來源物件,讓 Thread 把結果更新在物件中的 Field
> 這種作法比較很少見了,除非是特定的業務類物件,不然都會透過 Future 類處理,不會讓 Thread 內、外部的來源耦合在一起
- Future 類應用
> 本篇要講的主要類
- etc...
> 實際上還有許多 Kai 上述沒提及的方式可以得到相同結果,冗長內容就不多提在這裡了~
## 【Future】
在 1.5 版中 Java 新設計來處理非同步回傳結果的類,指派這個類去接收一個 Thread 的結果後,程式會往後繼續執行其他程序,且 Future 類可以在任何時刻被調用,並會等到其本身取得 Thread 回傳結果時作用。
但仍要注意,**主 Thread** 不能在此前結束。
Future 提供以下方便的 function 讓開發者使用:
| 方法名稱 | 作用 | 備註 |
| ---- | ---- | ---- |
| get() | 取得回傳結果 ||
| get(long timeout, TimeUnit unit) | 在等待的時間設定後取得回傳結果 | 強制加上 catch(TimeoutException) 如果無回傳結果則會拋出該錯誤類 |
| isDone() |判斷 Thread 是否**完成** |這個完成包含了: complete, terminate, cancel, exception 等結果,上述都會回傳 true|
| cancel(boolean mayInterruptRunning) |取消 Thread 執行|在 Thread 執行前會取消;完成後會無效果;執行中的則會斷開與 Future 類的連結|
| isCancel() | 判斷是否完成 cancel() 命令||
### 【範例】
**work.java**
```java=
package Thread.Future;
public class work {
private String name;
public work(String name){
this.name = name;
}
public String doWork(){
int totalWorkTime = (int)(10000*Math.random());
System.out.println(name + " start and it needs : " +totalWorkTime + " secs to finish.");
try {
Thread.sleep(totalWorkTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " done.");
return "After " + totalWorkTime + " secs, " + name + " done.";
}
}
```
**workExecutorService.java**
```java=
package Thread.Future;
import java.util.concurrent.*;
public class workExecutorService {
private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS,new ArrayBlockingQueue(3));
public Future<String> serviceAsync(work work){
return threadPoolExecutor.submit(()->service(work));
}
private String service(work work){
return work.doWork();
}
public boolean isFinish(){
return threadPoolExecutor.getActiveCount() <= 0;
}
public void doShutdown(){
threadPoolExecutor.shutdown();
try {
if(!threadPoolExecutor.awaitTermination(5000, TimeUnit.SECONDS))
return;
} catch (InterruptedException e) {
e.printStackTrace();
}
while(!threadPoolExecutor.isTerminated() || threadPoolExecutor.isTerminating()){
System.out.println("Is terminating.");
}
System.out.println("Is terminated.");
}
}
```
**test.java**
```java=
package Thread.Future;
import org.junit.jupiter.api.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.IntStream;
public class test {
private work work;
private final workExecutorService workExecutorService = new workExecutorService();
@DisplayName("Test Future : All In, All Out.")
@Test
public void testCase1(){
List<Future<String>> list = new ArrayList<>(10);
/*** 隨機出數且不重複 ***/
// IntStream.range(0, 10).parallel().forEach(
// number -> {
// work = new work("work_"+number);
// System.out.println("Add new work: work_" +number);
// list.add(workExecutorService.serviceAsync(work));
// }
// );
/*** 固定出數且不重複 ***/
for(int number = 0; number< 10; number++){
work = new work("work_"+number);
System.out.println("Add new work: work_" +number);
list.add(workExecutorService.serviceAsync(work));
}
System.out.println("Running Future List For-Loop.");
for(Future<String> future: list){
try {
/*** 一次觀看一種結果操作 ***/
// System.out.println(future.isDone());
// System.out.println(future.cancel(true)); // 須拿掉 try () catch {}
System.out.println(future.get().toString());
// System.out.println(future.get(100, TimeUnit.MILLISECONDS).toString());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
@BeforeAll
public static void BeforeAll(){
System.out.println("Test Start");
}
@AfterAll
public static void AfterAll(){
System.out.println("Test End");
}
}
```
### 【範例結果】
由以下結果可以看出,在一開始建立了 10 道 Threads 並且放入了 ThreadPool 中,ThreadPool 被設定成每次執行 3 道 Threads。
而後直接進入處理 Future 的 for-loop 中,但真正取出了結果的回應卻是在各個 Thread 執行完之後才逐一被取出來。
這正是因為在 Future 類的幫助下,開發者可不用理會程式是否會卡在等待 Thread 結果的程序上,轉而處理後續動作,而透過 Future 的特性,達成自動化處理 Thread 結果的方式。
```java=
/*** 固定出數且不重複的結果 ***/
Test Start
Add new work: work_0
Add new work: work_1
Add new work: work_2
Add new work: work_3
Add new work: work_4
Add new work: work_5
Add new work: work_6
Add new work: work_7
Add new work: work_8
Add new work: work_9
Running Future List For-Loop.
work_1 start and it needs : 5224 secs to finish.
work_2 start and it needs : 5690 secs to finish.
work_0 start and it needs : 4509 secs to finish.
work_0 done.
work_3 start and it needs : 1036 secs to finish.
After 4509 secs, work_0 done.
work_1 done.
work_4 start and it needs : 2997 secs to finish.
After 5224 secs, work_1 done.
work_3 done.
work_5 start and it needs : 7386 secs to finish.
work_2 done.
work_6 start and it needs : 7001 secs to finish.
After 5690 secs, work_2 done.
After 1036 secs, work_3 done.
work_4 done.
work_7 start and it needs : 1566 secs to finish.
After 2997 secs, work_4 done.
work_7 done.
work_8 start and it needs : 6081 secs to finish.
work_6 done.
work_9 start and it needs : 5523 secs to finish.
work_5 done.
After 7386 secs, work_5 done.
After 7001 secs, work_6 done.
After 1566 secs, work_7 done.
work_8 done.
After 6081 secs, work_8 done.
work_9 done.
After 5523 secs, work_9 done.
Test End
```
> **Executor.callable()** 會將所有送入的 Runnable 轉為 Callable 型別,再執行
> **ThreadPoolExecutor.submit()** 會將 **Callable** 類封裝為 **FutureTask**,執行完畢後,再進行回傳,因此才可以用 **List<Future<>>** 的方式取得 Thread 執行結果。
> FutureTask.get() 不為異步任務,因此會有阻塞性,可以從上方的結果看到所有的 **After XXXX secs, work_X done.** 都是依序列印出來的。
首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA)
###### tags: `Spring Boot`