接深入淺出多線程(2)在多線程交互的中,經常有一個線程需要得到另個一 線程的計算結果,我們常用的是Future異步模式來加以解決。
什麼是Future模式呢?Future 顧名思義,在金融行業叫期權,市場上有看跌 期權和看漲期權,你可以在現在(比如九月份)購買年底(十二月)的石油,假 如你買的是看漲期權,那麼如果石油真的漲了,你也可以在十二月份依照九月份 商定的價格購買。扯遠了,Future就是你可以拿到未來的結果。對於多線程,如 果線程A要等待線程B的結果,那麼線程A沒必要等待B,直到B有結果,可以先拿 到一個未來的Future,等B有結果是再取真實的結果。其實這個模式用的很多, 比如浏覽器下載圖片的時候,剛開始是不是通過模糊的圖片來代替最後的圖片, 等下載圖片的線程下載完圖片後在替換。如圖所示:
在沒有JDK1.5提供的Concurrent之前,我們通過自定義一個結果類,負責結 果持有。
如下面代碼:
package vincent.blogjava.net;
public class FutureResult {
private String result;
private boolean isFinish =false;
public String getResult() {
return result;
}
public synchronized void setResult(String result) {
this.result = result;
this.isFinish = true;
}
public synchronized boolean isFinish() {
return isFinish;
}
}
存儲結果值和是否完成的Flag。
package vincent.blogjava.net;
public class GenerateResultThread extends Thread{
FutureResult fr ;
public GenerateResultThread(FutureResult fr ){
this.fr = fr;
}
public void run(){
//模仿大量耗時計算後(5s)返回結果。
try {
System.out.println("GenerateResultThread開始進行計算了! ");
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
fr.setResult("ResultByGenerateResultThread");
}
}
計算具體業務邏輯並放回結果的線程。
package vincent.blogjava.net;
public class Main {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
FutureResult fr = new FutureResult();
new GenerateResultThread(fr).start();
//main線程無需等待,不會被阻塞。
//模仿 干自己的活 2s。
Thread.sleep(2000);
// 估計算完了吧 取取試試。
System.out.println("過來2s了,看看有結果嗎?");
if(!fr.isFinish()){System.out.println("還沒有完成呢! 繼續干自己 活吧!");}
//模仿 干自己的活 4s。
Thread.sleep(4000);
System.out.println("過來4s了,看看有結果嗎?");
if(fr.isFinish()){
System.out.println("完成了!");
System.out.println("Result:"+fr.getResult());
}
}
}
Main方法需要GenerateResultThread線程計算的結果,通過這 種模式,main線程不需要阻塞。結果如下:
GenerateResultThread開始進行計算了!
過來2s了,看看有結果嗎?
還沒有完成呢! 繼續干自己活吧!
過來4s了,看看有結果嗎?
完成了!
Result:ResultByGenerateResultThread
在JDK1.5 Concurrent 中,提供了這種Callable的機制。我們只要實現 Callable接口中的Call方法,Call方法是可以返回任意類型的結果的。如下:
package vincent.blogjava.net;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class ConcurrentImpl {
public static void main(String[] args) throws InterruptedException, Exception {
FutureTask fr = new FutureTask(new Returnresult());
new Thread(fr).start();
//main線程無需等待,不會被阻塞。
//模仿 干自己的活 2s。
Thread.sleep(2000);
// 估計算完了吧 取取試試。
System.out.println("過來2s了,看看有結果嗎?");
if(!fr.isDone()){System.out.println("還沒有完成呢! 繼續干自己活 吧!");}
//模仿 干自己的活 4s。
Thread.sleep(4000);
System.out.println("過來4s了,看看有結果嗎?");
if(fr.isDone()){
System.out.println("完成了!");
System.out.println("Result:"+fr.get());
}
}
}
class Returnresult implements Callable{
@Override
public Object call() throws Exception {
//模仿大量耗時計算後(5s)返回結果。
System.out.println("GenerateResultThread開始進行計算了! ");
Thread.sleep(11000);
return "ResultByGenerateResultThread";
}
}
Returnresult 實現了Callable接口,在Call方法中實現業務邏 輯,並返回結果。在Main方法裡面,初始化FutureTask 並將該Task作為 Runnable加入Thread後,啟動線程。得到跟剛才相同的效果。
注意: 通過JDK標准的Future後,沒有必要增加額外的Object來只有Result ,更加簡單明了,同時FutureTask還提供了Cancel的功能,我們持有FutureTask 引用後可以Cancel該線程。通過get()取值是,如果結果還沒有返回,將會阻塞 Main線程。
其實JDK 實現Future模式的秘密就在FutureTask類裡:
FutureTask是實現了Future 和Runnable,對了就是Runnbale接口,我們就可 以把它構造到Thread裡,啟動執行了。
看看,當 new Thread(new FutureTask(new Callable())).start 時:
看圖:
G
get 方法取result值,FutureTask 提供Timeout 功能,如果超時,拋出異常 。