併發模式(一)Future模式
並行設計模式是對一些常用的多執行緒結構的總結和抽象,與序列程式設計相比,並行程式更復雜。
前言
常用的併發設計模式有Future模式、Master-Worker模式、Guarded Suspension模式、不變模式、生產者-消費者模式,在多執行緒環境中,合理使用模式,可以提高程式效能,優化程式設計。
接下來會記錄這些模式的學習過程,一一成文,以便以後查閱和複習。
不變模式
不變模式的實現很簡單,這裡說明一下。不變模式天生就是多執行緒友好的。一個物件一旦被建立,則它的內部狀態將永遠不會發生改變,沒有一個執行緒可以修改其內部狀態和資料,同時內部狀態也不會自行發生改變。基於這些特性,不變模式的物件,在多執行緒環境中不需要同步控制。
主要使用場景:
- 當物件建立後,其內部狀態和資料不再發生任何變化;
- 物件需要被共享、被多執行緒頻繁訪問。
程式碼實現:
- final修飾類,確保不能被繼承,沒有子類;
- 所有屬性私有化,並用final標記,確保不會被修改;
- 移除setter方法和其他所有修改自身屬性的方法;
- 建構函式的引數包括所有的屬性,提供賦值的視窗。
在JDK中,所有基本型別的包裝類、String都是使用不變模式實現的。
Future模式
概念
Future模式是多執行緒設計常用的一種設計模式,類似商品訂單。商品下單後,會立即得到下單成功的通知,客戶不用等待後續商家的操作,只等配送到家即可,下單後到收到商品這段時間,客戶可以做其他事情,不用在家等著商品。Future模式也類似Ajax的非同步請求,不用等待處理結果。
處理流程
傳統處理流程
客戶端發出call請求,這個請求需要很長一段時間才能返回。客戶端一直等待著,直到資料返回,隨後進行其他業務處理。
Future模式流程
服務程式不需要等待資料處理完成便立即返回客戶端偽造的資料(相當於商品的訂單,而不是實際商品),客戶端拿到這個返回結果後,並不急於對其處理,而是利用等待的時間,呼叫其他業務邏輯。這就是Future模式的核心所在。
主要參與者
Main:系統啟動,呼叫client發出請求。
Client:返回Data物件,立即返回FutureData,並開啟ClientThread執行緒裝配RealData。
Data:返回資料的介面。
FutureData:Future資料,構造很快,但是是一個虛擬的資料,需要裝配RealData。
RealData:真實資料,其構造過程是比較慢的。
程式碼實現
Future模式結構圖
Main
//Future的簡單實現。
// 系統啟動,呼叫client發出請求
public class Main {
public static void main(String[] args) {
Client client = new Client();
//這裡會立即返回,因為得到的是FutureData,而不是RealData
Data data = client.request("name");
System.out.println("傳送請求完畢。。。");
try {
//模擬其他業務邏輯處理
//處理過程中,RealData被建立,充分利用了等待的時間。
System.out.println("Main 正在呼叫其他業務邏輯。。。");
Thread.sleep(1000);
System.out.println("Main 其他業務處理完成。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
//使用真實的資料
System.out.println("資料= " + data.getResult());
}
}
Client
//返回Data物件,立即返回FutureData,並開啟ClientThread執行緒裝配RealData
public class Client {
public Data request(final String requestStr){
final FutureData future=new FutureData();
//RealData構建過程很慢,所以在單獨的執行緒中進行
new Thread(){
@Override
public void run() {
RealData realData=new RealData(requestStr);
future.setRealData(realData);
}
}.start();
return future;//future會被立即返回
}
}
Data
//返回資料的介面
public interface Data {
public String getResult();
}
FutureData
//Future資料,構造很快,但是是一個虛擬的資料,需要裝配RealData
//FutureData是Future模式的核心,是RealData的真實代理,封裝了等待RealData的過程。
public class FutureData implements Data {
protected RealData realData=null;//FutureData是RealData的包裝
protected boolean isReady=false;
public synchronized void setRealData(RealData realData){
if (isReady){
return;
}
this.realData=realData;
isReady=true;
notifyAll();//RealData已被注入,通知getResult()
}
@Override
public synchronized String getResult() {
//等待RealData構造完成
while (!isReady){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return realData.result;//由RealData實現
}
}
RealData
//真是資料,其構造過程是比較慢的
public class RealData implements Data {
protected final String result;
public RealData(String para){
StringBuffer buffer=new StringBuffer();
System.out.println("RealData 正在構造真實資料。。。");
for (int i=0;i<10;i++){
buffer.append(para);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("RealData 真實資料構造完成。。。");
result=buffer.toString();
}
@Override
public String getResult() {
return result;
}
}
FutureData其實是RealData的一個代理,實現RealData的延遲效果。
JDK的內建實現
JDK內建的Future模式
程式碼優化
使用JDK內建的Future模式優化程式碼,首先要實現Callable這個介面,Data
、FutureData
、Client
物件就不需要了。
RealData
public class RealData implements Callable<String> {
private String para;
public RealData(String para) {
this.para = para;
}
//call中寫具體的業務邏輯
@Override
public String call() throws Exception {
//模擬真實業務邏輯,執行很慢
StringBuffer buffer=new StringBuffer();
System.out.println("RealData 正在構造真實資料。。。");
for (int i=0;i<10;i++){
buffer.append(para);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("RealData 真實資料構造完成。。。");
return buffer.toString();
}
}
Main
在Main方法中,直接通過RealData
構造FutureTask
,作為單獨的執行緒執行。
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//構造FutureTask
FutureTask<String> future=new FutureTask<String>(new RealData("name"));
ExecutorService executor= Executors.newFixedThreadPool(1);
//執行FutureTask,相當於client.request()傳送請求
//開啟執行緒進行RealData的call()執行
executor.submit(future);
System.out.println("傳送請求完畢");
try {
System.out.println("Main 正在呼叫其他業務邏輯。。。");
Thread.sleep(2000);
System.out.println("Main 其他業務處理完成。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
//使用真實的資料
//相當於data.getResult(),取得call()方法的返回值
//如果call()方法沒有執行完成,則依然會等待
System.out.println("資料= " + future.get());
}
}
Future模式的核心在於除了主函式中的等待時間,並使得原本需要等待的時間可以用於其他業務邏輯的處理,充分利用了計算機資源,提高系統性能。
參考資料
葛一鳴:Java程式效能優化-讓你的Java程式更快、更穩定