# 牛年自強計畫 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`