憑據管理
在日常生活中,我們經常會遇到一種情況,就是在我們提出需求之后,由于服務者還沒有準備好對應的實物交付給我們的時候,就會先給我們一張憑據,等到雙方約定的時間到了之后,我們帶著這個憑據就可以去獲取到我們想要的東西。例如在一個蛋糕店里我們定了一個蛋糕,店長給了我們一個憑據,然后在約定的時間我們去蛋糕店中取走我們定的蛋糕。這樣我們不需要在店里等待,而是去完成我們的其他工作。
在程序開發中假設有一個任務執行的時間比較長,通常需要我們等待這個任務完成之后才能進行后續的任務,這個時候服務的調用者就會一直等待。而我們利用上面的介紹的憑據的方式就完全可以解決調用者等待的問題。也就是JAVA中常說的Future模式。下面我們就來看看關于Java中的Future模式。
Future設計模式
Future設計模式是從JDK1.5開始出現的,在我們實現多線程操作的時候有常用的兩種方式,一種是實現Runnbale接口,一種是繼承Thread類,而我們不是太常用的一種方式就是Call接口的方式,這種方式就是類似于Future模式。其實現類圖如下。
下面我們就來看看Future設計模式的具體實現方式。
接口邏輯定義
Future接口
根據之前的分析我們的票據應當具有兩個功能,一個功能是可以通過它獲取到我們想要的結果,另一個功能就是通過它我們可以知道我們想要的蛋糕是否制作完成。如下所示。
public interface Future {// 用于返回計算之后的結果T get() throws InterruptedException;// 用于判斷任務是否正常執行boolean done();
FutureService接口
FutureService 的主要作用就是任務的提交,而提交任務的方式有兩種,一種是不需要返回值的,一種是需要返回值的。代碼如下。
public interface FutureService {// 提交不需要進行返回的任務的時候 Future的get方法返回為空Future submit(Runnable runnable);// 提交需要返回值的任務的時候 Future的get方法就是獲取到返回值Future submit(Tasktask,IN input);// 靜態方法獲取到實現實例對象static FutureService newServcie(){return new FutureServiceImpl<>();
Task接口
有了票據,有了服務人員,接下來就是需要有我們需要完成的任務是什么,而Task就是用來提供給調用者實現計算邏輯的任務。可以接受一個參數并且返回最后的結果。有點類似于Callable接口
@FunctionalInterfacepublic interface Task {//給定一個參數經過計算之后得到一個結果OUT get(IN input);
Future程序實現
Future接口實現
會發現這個接口實現除了實現Future接口的兩個方法之外還增加了一個finish的方法,用來完成任務通知操作。
public class FutureTask implements Future {// 返回結果private T result;//任務是否完成private boolean isDone = false;// 對象鎖private final Object LOCK = new Object();@Overridepublic T get() throws InterruptedException {synchronized (LOCK){//當任務還沒有完成的時候,調用get方法會被掛起進入阻塞等待while (!isDone){LOCK.wait();// 返回計算結果return result;protected void finish(T result){synchronized (LOCK){//balking 設計模式if (isDone){return;// 計算完成,為result指定結果,并且將isDone設置為true,同時喚起等待中的線程this.result = result;this.isDone = true;LOCK.notifyAll();@Overridepublic boolean done() {return isDone;
在FutureTask中使用了線程間的通信wait和notifyAll,當任務沒有完成之前通過get方法獲取接口,調用方會進入到阻塞狀態,直到任務完成之后收到線程喚醒,finish方法接收到任務完成的通知,然后喚醒因為調用了get方法而進入阻塞的線程。
FutureService實現
public class FutureServiceImpl implements FutureService {private final static String FUTURE_THREAD_PREFIX = "FUTURE-";private final AtomicInteger nextCounter = new AtomicInteger(0);private String getNextName(){return FUTURE_THREAD_PREFIX+nextCounter.getAndIncrement();@Overridepublic Future submit(Runnable runnable) {final FutureTask future = new FutureTask<>();new Thread(()->{runnable.run();// 任務執行結束之后將null作為參數返回future.finish(null);},getNextName()).start();return future;@Overridepublic Future submit(Task task, IN input) {final FutureTask future = new FutureTask<>();new Thread(()->{OUT result = task.get(input);// 任務執行結束之后,將真實的結果通過finish的方式傳遞給futurefuture.finish(result);},getNextName()).start();return future;
Future設計模式測試
這里我們提交一個沒有返回值的任務,代碼如下,
public class FutureTest {public static void main(String[] args) throws InterruptedException {FutureService service = FutureService.newServcie();Future future = service.submit(()->{try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();System.out.println("任務完成!");future.get();
提交一個有返回值的任務
public class FutureTest {public static void main(String[] args) throws InterruptedException {FutureService service = FutureService.newServcie();Future future = service.submit(input->{try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();return input.length();},"Hello");System.out.println(future.get());
升級獲取結果的方法
從上面兩個測試的返回結果來看,如果我們調用了future的get方法,那么我們的程序就會進入到阻塞的狀態,這個操作與我們的預期不太相符合,這個也是整個的Future模式一直存在的問題。那么我們如何去改進這個獲取get方法等待的問題呢?這就引入了一個Callback的機制。代碼如下
@Overridepublic Future submit(Task task, IN input, Callback callback) {final FutureTask future = new FutureTask<>();new Thread(()->{OUT result = task.get(input);// 任務執行結束之后,將真實的結果通過finish的方式傳遞給futurefuture.finish(result);if (null!=callback){callback.call(result);},getNextName()).start();return future;
測試效果,會發現我們課可以不使用get方法就可以完成執行結果的獲取。
public class FutureTest {public static void main(String[] args) throws InterruptedException {FutureService service = FutureService.newServcie();service.submit(input->{try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();return input.length();},"Hello",System.out::println);
總結
在之前的分享中很多的地方我們都提到了Future設計模式,通過這篇文章我們也進一步的了解了關于Future設計模式的思想。其核心思想就是模擬了一個憑據場景,通過這種方式來實現對CPU的高效利用。當然這里我們給出的只是一個基礎演示版本,其中的存在的問題還有很多,有興趣的讀者可以自己思考相關內容的優化。