網上講解該設計模式的文章非常地多,很多講的內容、技術都比我全面和深入!但既然學習,我還是想進行一下總結!希望閱讀本文章的你可以獲得一點啟發!
說到併發程式設計,我們都會覺得,哇,併發程式設計是真的難!可事實真的是那樣子嗎?我覺得不是,一門技術出來,只要你有那個決心去攻克它,那麼你就贏了。也就是我們常說的:“穩住,猥瑣發育,別浪,我們能贏!”,學習的過程中肯定會遇到一些難點,多找人溝通交流,相互進步是非常好的途徑,小編希望你們在閱讀我總結的文章之後能和小編一起交流,不甚榮幸。
一、什麼是非同步?
講解設計模式之前我先講下非同步,非同步和同步是對立的,這兩個概念其實是不難理解的,關鍵是同步和非同步究竟是怎樣在程式中體現,才是我們最好奇,最想知道的。
同步
:就是當任務A依賴於任務B的執行時,必須等待任務B執行完畢之後任務A才繼續執行,此過程任務A被阻塞。任務要麼都成功,要麼都失敗!想一想我們打電話的情景即可!
非同步
:任務A呼叫任務B,任務A不需要等到任務B執行完畢,任務B只是返回一個虛擬的結果給任務A,使得任務A能夠繼續做其他事情,等到任務B執行完成之後再通知任務A(回撥)或者是任務A主動去請求任務B要結果。想一想發簡訊的情景即可!
二、Future
非同步設計模式
首先我在網上找到了一張圖,分別對比了非同步
和同步
的時序區別:
那麼我們怎麼實現呢?先貼一張UML圖再說:
講解例項之前,我覺得有必要說一些先決條件:
- 1、
notify/wait
的使用,每個java物件都擁有這兩個方法,呼叫這兩個方法必須先獲得監視器,常用的做法是在synchronized
同步塊中進行呼叫 - 2、
生產者/消費者
模型,其實這個可有可無,主要是不要寫出在if(塊中)
呼叫notify/wait
方法,而要改為while
我所有的邏輯註釋都穿插在下面的程式碼中,如果有想要一起交流的問題,歡迎評論,歡迎評論,歡迎評論!
Main.java
package com.wokao66.future;
/**
* 持有一個客戶端Client物件,傳送請求
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public class Main {
public static void main(String[] args) throws Exception {
/**
* 持有一個客戶端Client物件
*/
Client client = new Client();
/**
* 返回一個虛擬的資料(這是非同步返回的,虛擬的,不是真實的,但必須持有真實資料物件realData,方便後面獲取請求結果)
*/
Data virtualData = client.request("我要下單!!!!");
/**
* 睡眠5秒
*/
try {
Thread.sleep(5000);
} catch (InterruptedException e) {}
/**
* 我現在想獲得真實資料了
*/
String realData = virtualData.getResultData();
System.err.println("真實資料為:" + realData);
}
}
複製程式碼
Data.java
package com.wokao66.future;
/**
* 對返回資料的簡單抽象(虛擬資料和真實資料都必須實現該介面)
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public interface Data {
/**
* 獲取資料的操作,至於是虛擬的,還是真實的,我不用管,讓實現類去決定
* @throws Exception
*/
public abstract String getResultData() throws Exception;
}
複製程式碼
VirtualData.java
package com.wokao66.future;
/**
* 虛擬的資料(非同步返回給客戶端)
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public class VirtualData implements Data {
/**
* 想象一下,如果你這裡返回的VirtualData不包含對RealData的引用,那麼當客戶端需要獲取真實資料時,你的資料從何而來???
*/
private RealData realData = null;
/**
* 預設是還沒有準備好資料嘛!要有個狀態來跟蹤
*/
private boolean isReady = false;
/**
* 注入RealData,這個RealData
* @param realData
*/
public synchronized void setRealData(RealData realData) {
System.err.println("獲得鎖");
/**
* 如果還沒有準備好,我就需要
*/
if (isReady) {
return;
}
this.realData = realData;
isReady = true;//我已經準備好了
notify();//通知所有阻塞的執行緒
}
/**
* 重寫獲取資料的方式
* @throws Exception
*/
@Override
public synchronized String getResultData() throws Exception {
/**
* 如果客戶端呼叫的時候我還沒有注入真實資料,那麼就一直阻塞
*/
while (!isReady) {
//呼叫wait必須先獲得物件的鎖,所以
wait();
}
return realData.getResultData();
}
}
複製程式碼
RealData.java
package com.wokao66.future;
/**
* 這個類表示你具體的業務操作,比如重資料庫查詢資料
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public class RealData implements Data {
/**
* 請求名
*/
private String readData;
public RealData(String readData) {
/**
* 我這裡先休眠10秒,表示一個耗時的操作
*/
try {
Thread.sleep(10000);
} catch (InterruptedException e) {}
this.readData = "呼叫名為 : " + readData + " , " + "真實的資料為 : realData";
}
@Override
public String getResultData() {
return readData;
}
}
複製程式碼
Client.java
package com.wokao66.future;
/**
* 表示我們的客戶端程式嘛!負責發起呼叫請求
* @author: huangjiawei
* @since: 2018年4月2日
* @version: $Revision$ $Date$ $LastChangedBy$
*/
public class Client {
/**
* 表示客戶端的請求
* @param name 具體的請求名稱
* @return
*/
public Data request(String name) {
/**
* 宣告一個虛擬的資料
*/
VirtualData virtualData = new VirtualData();
/**
* 當你呼叫請求時,我後臺默默開啟一個執行緒去處理真實操作
*/
new Thread(new Runnable() {
@Override
public void run() {
System.err.println("我偷偷摸摸地請求後臺獲取資料的操作,該操作可能會執行很長的時間");
System.err.println("我不管了,先返回結果給呼叫方");
RealData realData = new RealData(name);
//下面兩句的執行順序是不一樣的
//啟動執行緒
virtualData.setRealData(realData);
}
}).start();
//我先返回一個虛擬的資料給你,真的資料等我獲取完成之後你再過來取
return virtualData;
}
}
複製程式碼
執行結果
下單成功...............
我偷偷摸摸地請求後臺獲取資料的操作,該操作可能會執行很長的時間
我不管了,先返回結果給呼叫方
獲得鎖
真實資料為:呼叫名為 : 我要下單!!!! , 真實的資料為 : realData
複製程式碼
不知道大家有沒有思考過,上面Main.java
中String realData = virtualData.getResultData();
是一個本地物件,只能在本機執行,那如果是基於tcp的網路傳輸呢?可以採用個推,websocket等方式進行,dubbo
目測也是可以的!對了,突然想起來,Java EE 7
開始已經支援非同步Servlet
了,詳細的可以自行檢視相關文件!
三、Java 中的非同步併發程式設計
非同步執行結果無非就有兩種形式:
- 1、執行之後有回撥
- 2、執行之後沒有回撥
有回撥
指的是客戶端建立相應的監聽器Listener
,服務端執行非同步Future
之後主動回撥客戶端的回撥函式
無回撥
即我們前面例子講的,我們需要自己主動去請求服務端判斷是否已經成功了
package com.wokao66.javafuture;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test {
public static void main(String[] args) {
/**
* 新建一個執行緒池
*/
ExecutorService executor = Executors.newFixedThreadPool(1);
/**
* 建立一個任務
*/
Task task = new Task();
/**
* 將任務提交給執行緒池
*/
Future<Integer> result = executor.submit(task);
//這裡會保證所有子執行緒執行完畢再關閉
executor.shutdown();
try {
/**
* 模擬執行其它操作
*/
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("我在執行其他操作........");
try {
System.out.println("任務的執行結果是:" + result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class Task implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("子執行緒在進行計算");
/**
* 模擬執行耗時操作
*/
Thread.sleep(10000);
return 100;
}
}
複製程式碼
執行結果
子執行緒在進行計算
我在執行其他操作........
任務的執行結果是:100
複製程式碼
同時java也可以使用FutureTask
來建立任務,FutureTask
同時實現Runnable
和Callable
介面,非常方便!
謝謝閱讀!