深入理解[Future模式]原理與技術
1.Future模式
Future模式和多執行緒技術密切相關,可以說是利用多執行緒技術優化程式的一個例項。
在程式設計中,當某一段程式提交了一個請求,期望得到一個答覆。但非常不幸的是,服務程式對這個請求的處理可能比較慢,比如,這個請求可能是通過網際網路、HTTP或者Web Service等並不高效的方式呼叫的。在傳統的單執行緒環境下,呼叫函式是同步的,也就是說它必須等到服務程式返回結束後,才能進行其他處理。而在Future模式下,呼叫方式改為非同步的,而原先等待返回的時間段,在主呼叫函式中,則可能用於處理其它事務。示例程式:
(1)Main方法的實現
main方法主要負責呼叫Client發起請求,並使用返回的資料:
public class Future { public static void main(String[] args) { Client client = new Client(); Data data = client.request("name"); System.out.println("請求完畢 "+System.currentTimeMillis()); //...這裡做一些其它任務 System.out.println("資料:"+data.getResult()); System.out.println("獲取完畢 "+System.currentTimeMillis()); } }
(2)Client的實現
client主要實現了獲取FutureData,開啟構造RealData的執行緒,並在接受請求後,很快的返回FutureData。
public class Client { public Data request(String queryStr){ FutureData futureData = new FutureData(); new Thread(new Runnable() { @Override public void run() { RealData realData =new RealData(queryStr); futureData.setRealData(realData); } }).start(); return futureData; } }
(3)Data的實現
Data是一個介面,提供了getResult()方法。
public interface Data { String getResult(); }
(4)FutureData的實現
FutureData 實現了一個快速返回的RealData 包裝。它只是一個包裝,或者說是一個RealData 的虛擬實現 。因此,它可以很快被構造並返回。當使用FutureData的getResult()方法時,程式會阻塞,等待RealData()被注入到程式中,才使用RealData的getResult()方法返回。
public class FutureData implements Data { privateRealData realData = null; privateboolean isReady = false; synchronized public void setRealData(RealData realData){ if (isReady){ return; } this.realData = realData; isReady = true; notifyAll(); //通知所有等待的執行緒繼續執行 } @Override synchronized public String getResult() { while (!isReady){ try { System.out.print("...waiting..."); wait(); //使當前執行緒在此處進行等待,直到被通知後繼續執行 } catch (InterruptedException e) { e.printStackTrace(); } } return realData.result; } }
(5)RealData 的實現
RealData 是最終需要使用的資料模型,它的構造很慢。在這裡,使用sleep()函式模擬這個過程。
public class RealData implements Data { protected String result; public RealData(String para) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } result = "["+para+"]"; } @Override public String getResult() { return result; } }
執行結果:
請求完畢 1537520554813 ...waiting... 資料:[name] 獲取完畢 1537520555890
程式執行的流程是Main執行緒去獲取資料,但是資料還在處理中,於是Main執行緒進入等待狀態,當資料處理完並通知等待所有等待的執行緒之後,Main執行緒得以繼續執行下去。
2.JDK的內建實現
Future模式如此常用,以至於在JDK的併發包中,就已經內建了一種Future模式的實現了。
示例程式:
public class RealData implements Callable<String> { private String para; public RealData(String para) { this.para = para; } @Override public String call() throws Exception { //這裡是真實的業務邏輯 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "["+para+"]"; } public static void main(String[] args) throws ExecutionException, InterruptedException { //傳入RealData到FutureTask FutureTask<String> futureTask = new FutureTask<String>(new RealData("name")); //建立一個執行緒池 ExecutorService executorService = Executors.newFixedThreadPool(1); //在這裡開啟執行緒執行RealData的call()方法 executorService.submit(futureTask); System.out.println("請求完畢 "+System.currentTimeMillis()); //...這裡進行一些其它操作 System.out.println("資料:"+futureTask.get()); System.out.println("獲取完畢 "+System.currentTimeMillis()); //啟動一個有序的關閉,之前提交的任務將被執行,但是不會接受新的任務。 executorService.shutdown(); } }
執行結果:
請求完畢 1537521833970 資料:[name] 獲取完畢 1537521834977
Callable介面是一個使用者自定義實現的介面。在應用程式中,通過實現Callable介面的call()方法,指定FutureTask的實際內容和返回物件。
Future介面提供的執行緒控制功能有:
//取消任務 boolean cancel(boolean mayInterruptIfRunning) //是否已經取消 boolean isCancelled() //是否已經完成 boolean isDone() //取得返回物件 V get() throws InterruptedException, ExecutionException //取得返回物件,可以設定超時時間 V get(long timeout, TimeUnit unit)
總結
Future模式的核心在於去除了主函式中的等待時間,並使得原來需要等待的時間段可以用於處理其他的業務邏輯,從而充分利用計算機資源。