詳細參見葛一名老師的《Java程序性能優化》
Futrue模式:對於多線程,如果線程A要等待線程B的結果,那么線程A沒必要等待B,直到B有結果,可以先拿到一個未來的Future,等B有結果是再取真實的結果。
在多線程中經常舉的一個例子就是:網絡圖片的下載,剛開始是通過模糊的圖片來代替最后的圖片,等下載圖片的線程下載完圖片后在替換。而在這個過程中可以做一些其他的事情。
首先客戶端向服務器請求RealSubject,但是這個資源的創建是非常耗時的,怎么辦呢?這種情況下,首先返回Client一個FutureSubject,以滿足客戶端的需求,於此同時呢,Future會通過另外一個Thread 去構造一個真正的資源,資源准備完畢之后,在給future一個通知。如果客戶端急於獲取這個真正的資源,那么就會阻塞客戶端的其他所有線程,等待資源准備完畢。
公共數據接口,FutureData和RealData都要實現。
1 public interface Data { 2 public abstract String getContent(); 3 }
FutureData,當有線程想要獲取RealData的時候,程序會被阻塞。等到RealData被注入才會使用getReal()方法。
1 package com.volshell.future; 2 3 public class FutureData implements Data { 4 5 protected RealData realData = null; 6 protected boolean isReady = false; 7 8 @Override 9 public synchronized String getResult() { 10 // TODO Auto-generated method stub 11 while (!isReady) { 12 try { 13 wait(); 14 } catch (Exception e) { 15 // TODO: handle exception 16 } 17 } 18 return realData.result; 19 } 20 21 public synchronized void setRealData(RealData realData) { 22 if (isReady) 23 return; 24 this.realData = realData; 25 isReady = true; 26 notifyAll(); 27 } 28 }
真實數據RealData
1 package com.volshell.future; 2 3 public class RealData implements Data { 4 protected String result; 5 6 public RealData(String para) { 7 StringBuffer sb = new StringBuffer(); 8 for (int i = 0; i < 10; i++) { 9 sb.append(para); 10 try { 11 Thread.sleep(100); 12 } catch (Exception e) { 13 // TODO: handle exception 14 } 15 result = sb.toString(); 16 } 17 } 18 19 @Override 20 public String getResult() { 21 // TODO Auto-generated method stub 22 return result; 23 } 24 25 }
客戶端程序:
1 package com.volshell.future; 2 3 public class Client { 4 public Data request(final String request){ 5 final FutureData future = new FutureData(); 6 new Thread(){ 7 public void run() { 8 9 RealData reaData = new RealData(request); 10 future.setRealData(reaData); 11 }; 12 }.start(); 13 return future; 14 } 15 }
調用者:
1 package com.volshell.future; 2 3 public class Main { 4 public static void main(String[] args) { 5 Client client = new Client(); 6 Data data = client.request("name"); 7 System.out.println("請求完畢!!"); 8 try { 9 Thread.sleep(2000); 10 } catch (Exception e) { 11 // TODO: handle exception 12 } 13 System.out.println("獲取的數據:" +data.getResult()); 14 } 15 }
調用者請求資源,client.request("name"); 完成對數據的准備
當要獲取資源的時候,data.getResult() ,如果資源沒有准備好isReady = false;那么就會阻塞該線程。直到資源獲取然后該線程被喚醒。
今天又重新了解了future模式。
1 package com.volshell.future2; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.concurrent.Callable; 6 import java.util.concurrent.ExecutionException; 7 import java.util.concurrent.ExecutorService; 8 import java.util.concurrent.Executors; 9 import java.util.concurrent.Future; 10 11 public class FutureTest2 { 12 private static class Task implements Callable<String> { 13 @Override 14 public String call() throws Exception { 15 // 模擬真實事務的處理過程,這個過程是非常耗時的。 16 Thread.sleep(5000); 17 return "call return "; 18 } 19 } 20 21 public static void main(String[] args) throws InterruptedException, 22 ExecutionException { 23 List<Future<String>> futures = new ArrayList<Future<String>>(); 24 ExecutorService executorService = Executors.newCachedThreadPool(); 25 26 System.out.println("已經提交資源申請"); 27 for (int i = 0; i < 10; i++) { 28 futures.add(executorService.submit(new Task())); 29 } 30 31 for (Future<String> future : futures) { 32 // 判斷資源是不是已經准備完畢,准備完畢直接獲取。 33 if (!future.isDone()) { 34 System.out.println("資源還沒有准備好"); 35 } 36 System.out.println(future.get()); 37 } 38 executorService.shutdown(); 39 } 40 }
其中的核心就是Callable中的call方法,這個和Runnable中的run 非常類似。
Runnable和Callable都是接口
不同之處:
1.Callable可以返回一個類型V,而Runnable不可以
2.Callable能夠拋出checked exception,而Runnable不可以。
3.Runnable是自從java1.1就有了,而Callable是1.5之后才加上去的
4.Callable和Runnable都可以應用於executors。而Thread類只支持Runnable.
上面只是簡單的不同,其實這兩個接口在用起來差別還是很大的。Callable與executors聯合在一起,在任務完成時可立刻獲得一個更新了的Future。而Runable卻要自己處理
Future接口,一般都是取回Callable執行的狀態用的。其中的主要方法:
- cancel,取消Callable的執行,當Callable還沒有完成時
- get,獲得Callable的返回值
- isCanceled,判斷是否取消了
- isDone,判斷是否完成
用Executor來構建線程池,應該要做的事:
1).調用Executors類中的靜態方法newCachedThreadPool(必要時創建新 線程,空閑線程會被保留60秒)或newFixedThreadPool(包含固定數量的線程池)等,返回的是一個實現了ExecutorService 接口的ThreadPoolExecutor類或者是一個實現了ScheduledExecutorServiece接口的類對象。
2).調用submit提交Runnable或Callable對象。
3).如果想要取消一個任務,或如果提交Callable對象,那就要保存好返回的Future對象。
4).當不再提交任何任務時,調用shutdown方法。