咱們知道多線程能夠實現同時執行多個任務(只是看起來是同時,實際上是CPU的時間片切換特別快咱們沒感受而已)。java
如今假設一個作飯的場景,你沒有廚具也沒有食材。你能夠去網上買一個廚具,可是這段時間,你不須要閒着啊,能夠同時去超市買食材。多線程
設想這是兩個線程,主線程去買食材,而後開啓一個子線程去買廚具。可是,子線程是須要返回一個廚具的。 若是用普通的線程,只有一個Run方法,而Run方法是沒有返回值的,這個時候該怎麼辦呢?異步
咱們就能夠用JDK提供的Future模式。在主線程買完食材以後,能夠主動去獲取子線程的廚具。(本文認爲讀者瞭解Future,所以不對Future用法作過多介紹)ide
代碼以下:測試
public class FutureCook { static class Chuju { } static class Shicai{ } public static void cook(Chuju chuju,Shicai shicai){ System.out.println("最後:烹飪中..."); } public static void main(String[] args) throws InterruptedException, ExecutionException { //第一步,網購廚具 Callable<Chuju> shopping = new Callable<Chuju>(){ @Override public Chuju call() throws Exception { System.out.println("第一步:下單"); System.out.println("第一步:等待送貨"); Thread.sleep(5000); //模擬送貨時間 System.out.println("第一步:快遞送到"); return new Chuju(); } }; FutureTask<Chuju> task = new FutureTask<Chuju>(shopping); new Thread(task).start(); //第二步,購買食材 Thread.sleep(2000); Shicai shicai = new Shicai(); System.out.println("第二步:食材到位"); //第三步,烹飪 if(!task.isDone()){ //是否廚具到位 System.out.println("第三步:廚具還沒到,請等待,也能夠取消"); //① // task.cancel(true); // System.out.println("已取消"); // return; } //嘗試獲取結果,若是獲取不到,就會進入等待狀態 // 即main線程等待子線程執行結束才能繼續往下執行 Chuju chuju = task.get(); System.out.println("第三步:廚具到位,能夠烹飪了"); cook(chuju,shicai); } }
返回結果:this
第一步:下單 第一步:等待送貨 第二步:食材到位 第三步:廚具還沒到,請等待,也能夠取消 第一步:快遞送到 第三步:廚具到位,能夠烹飪了 最後:烹飪中...
以上代碼表示,子線程購買廚具消耗的時間比較長(假定5秒),而主線程購買食材比較快(2秒),因此我在第三步烹飪以前,先去判斷一下買廚具的線程是否執行完畢。此處確定返回false,而後主線程能夠選擇繼續等待,也能夠選擇取消。(把①註釋打開便可測試取消)線程
咱們能夠看到,利用Future模式,能夠把本來同步執行的任務改成異步執行,能夠充分利用CPU資源,提升效率。代理
如今,我用wait、notify的方式來實現和以上Future模式如出一轍的效果。code
大概思想就是,建立一個FutureClient端去發起請求,經過FutureData先當即返回一個結果(此時至關於只返回一個請求成功的通知),而後再去開啓一個線程異步地執行任務,獲取真實數據RealData。此時,主線程能夠繼續執行其餘任務,當須要數據的時候,就能夠調用get方法拿到真實數據。對象
1)定義一個數據接口,包含獲取數據的get方法,判斷任務是否執行完畢的isDone方法,和取消任務的cancel方法。
public interface Data<T> { T get(); boolean isDone(); boolean cancel(); }
2)定義真實數據的類,實現Data接口,用來執行實際的任務和返回真實數據。
public class RealData<T> implements Data<T>{ private T result ; public RealData (){ this.prepare(); } private void prepare() { //準備數據階段,只有準備完成以後才能夠繼續往下走 try { System.out.println("第一步:下單"); System.out.println("第一步:等待送貨"); Thread.sleep(5000); System.out.println("第一步:快遞送到"); } catch (InterruptedException e) { System.out.println("被中斷:"+e); //從新設置中斷狀態 Thread.currentThread().interrupt(); } Main.Chuju chuju = new Main.Chuju(); result = (T)chuju; } @Override public T get() { return result; } @Override public boolean isDone() { return false; } @Override public boolean cancel() { return true; } }
prepare方法用來準備數據,其實就是執行的實際任務。get方法用來返回任務的執行結果。
3)定義一個代理類FutureData用於給請求端FutureClient暫時返回一個假數據。等真實數據拿到以後,再裝載真實數據。
public class FutureData<T> implements Data<T>{ private RealData<T> realData ; private boolean isReady = false; private Thread runningThread; public synchronized void setRealData(RealData realData) { //若是已經裝載完畢了,就直接返回 if(isReady){ return; } //若是沒裝載,進行裝載真實對象 this.realData = realData; isReady = true; //進行通知 notify(); } @Override public synchronized T get() { //若是沒裝載好 程序就一直處於阻塞狀態 while(!isReady){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //裝載好直接獲取數據便可 return realData.get(); } public boolean isDone() { return isReady; } @Override public boolean cancel() { if(isReady){ return false; } runningThread.interrupt(); return true; } public void setRunningThread(){ runningThread = Thread.currentThread(); } }
若是get方法被調用,就會去判斷數據是否已經被加載好(即判斷isReady的值),若是沒有的話就調用wait方法進入等待。
setRealData用於去加載真實的數據,加載完畢以後就把isReady設置爲true,而後調用notify方法通知正在等待的線程。此時,get方法收到通知就繼續執行,而後返回真實數據realData.get().
另外也簡單的實現了一個取消任務的方法cancel,去中斷正在執行子任務的線程。
4)FutureClient客戶端用於發起請求,異步執行任務。
public class FutureClient { public Data call(){ //建立一個代理對象FutureData,先返回給客戶端(不管是否有值) final FutureData futureData = new FutureData(); //啓動一個新的線程,去異步加載真實的對象 new Thread(new Runnable() { @Override public void run() { //此處注意須要記錄一下異步加載真實數據的線程,以便後續能夠取消任務。 futureData.setRunningThread(); RealData realData = new RealData(); //等真實數據處理完畢以後,把結果賦值給代理對象 futureData.setRealData(realData); } }).start(); return futureData; } }
5)測試
public class Main { static class Chuju{ } static class Shicai{ } public static void main(String[] args) throws InterruptedException { FutureClient fc = new FutureClient(); Data data = fc.call(); Thread.sleep(2000); Shicai shicai = new Shicai(); System.out.println("第二步:食材到位"); if(!data.isDone()){ System.out.println("第三步:廚具還沒到,請等待或者取消"); //② // data.cancel(); // System.out.println("已取消"); // return; } //真正須要數據的時候,再去獲取 Chuju chuju = (Chuju)data.get(); System.out.println("第三步:廚具到位,能夠烹飪了"); cook(chuju,shicai); } public static void cook (Chuju chuju, Shicai shicai){ System.out.println("最後:烹飪中..."); } }
執行結果和用JDK提供的Future模式是如出一轍的。咱們也能夠把②出的代碼打開,測試任務取消的結果。
第一步:下單 第一步:等待送貨 第二步:食材到位 第三步:廚具還沒到,請等待或者取消 已取消 被中斷:java.lang.InterruptedException: sleep interrupted
執行取消以後,執行RealData的子線程就會被中斷,而後結束任務。