1. 程式人生 > >Java高並發程序設計學習筆記(七):並行設計模式

Java高並發程序設計學習筆記(七):並行設計模式

@override ptime fixed quest detail cli vat false running

轉自:https://blog.csdn.net/dataiyangu/article/details/87123586

什麽是設計模式
架構模式
設計模式
代碼模式(成例 Idiom)
單例模式
普通單例
假如單例中有某個字段
改進的單例
代理模式再升級
不變模式
不變模式是如何實現的
不變模式的案例
Future模式
核心思想是異步調用
舉個栗子
JDK對Future模式的支持
通過callable實現future
更加簡便的方式實現future
生產者消費者
簡單代碼實現
什麽是設計模式
在軟件工程中,設計模式(design pattern)是對軟件設計中普遍存在(反復出現)的各種問題 ,所提出的解決方案。這個術語是由埃裏希·伽瑪(Erich Gamma)等人在1990年代從建築設計領 域引入到計算機科學的。

Richard Helm, Ralph Johnson ,John Vlissides (Gof) 和Gamma合稱四人幫
《設計模式:可復用面向對象軟件的基礎》 收錄 23種模式
– 觀察者模式
– 策略模式
– 裝飾者模式
– 享元模式
– 模板方法
– …

架構模式
– MVC
– 分層

設計模式
– 提煉系統中的組件

代碼模式(成例 Idiom)
– 低層次,與編碼直接相關
– 如DCL

class Person {
String name;
int birthYear;

byte[] raw;
public boolean equals(Object obj){
if (!obj instanceof Person)
return false;
Person other = (Person)obj;
return name.equals(other.name)
&& birthYear == other.birthYear
&& Arrays.equals(raw, other.raw);
}

public int hashCode(){
...

單例模式
單例對象的類必須保證只有一個實例存在。許多時候整個系統只需要擁有一個的全局對象,這樣 有利於我們協調系統整體的行為
比如:全局信息配置
在多線程中通過單例模式,防止多個線程多次創建對象。

普通單例
public class Singleton {
private Singleton(){
System.out.println("Singleton is create");
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
1
2
3
4
5
6
7
8
9
何時產生實例 不好控制

假如單例中有某個字段
一般來說,產生實例的時間是調用getinstance方法的時候,但是實際上是Singleton對象第一次被訪問的時候。

public class Singleton {
public static int STATUS=1;
private Singleton(){
System.out.println("Singleton is create");
}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}

System.out.println(Singleton.STATUS);
1
Singleton is create 1
1
想要輸出STATUS這個字段,去訪問了Singleton這個類,會自動創建一個實例,這是一個不好的地方。

改進的單例
public class LazySingleton {
private LazySingleton() {
System.out.println("LazySingleton is create");
}
private static LazySingleton instance = null;
public static synchronized LazySingleton getInstance() {
if (instance == null)
instance = new LazySingleton();
return instance;
}
}

因為上面的一點小bug,所以衍生出了這種單例模式,只有是第一次(instance=null)的時候才會進行初始化,是一個延遲加載的過程,同時防止多線程進入此類而創建多個實例,所以在方法上加入了synchronized關鍵字,保證當有一個線程進來的時候,其他的線程進不來,所以只有一個線程能夠進入if (instance == null)這句話,不會多個線程同時進行判斷。但是synchronized這個鎖,可能對於高並發,可能有點影響。

代理模式再升級
public class StaticSingleton {
private StaticSingleton(){
System.out.println("StaticSingleton is create");
}
private static class SingletonHolder {
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return SingletonHolder.instance;
}
}


為了避免上面的synchronized帶來的高並發的性能問題,衍生出了這種方式。
將new StaticSingleton放到內部類中,調用getInstance方法的時候再訪問StaticSingleton類中的instance,再new StaticSingleton來進行初始化,如果有一個static的STATUS的變量的時候,去訪問它,是不會創建本類的實例的,因為並沒有對內部類進行初始化,所以,只有通過訪問getInstance()這個方法的時候才會進行初始化。
通過這種方法也起到一種延遲加載的效果,而且沒有高並發的性能問題,因為並沒有加鎖。

不變模式
一個類的內部狀態創建後,在整個生命期間都不會發生變化時,就是不變類
不變模式不需要同步,因為不變模式是一個只讀的對象。

不變模式是如何實現的
public final class Product {
//確保無子類
private final String no;
//私有屬性,不會被其他對象獲取
private final String name;
//final保證屬性不會被2次賦值
private final double price;
public Product(String no, String name, double price) {//在創建對象時,必須指定數據
super();
//因為創建之後,無法進行修改 this.no = no;
this.name = name; this.price = price;
}
public String getNo() {
return no;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}

將類變成final的,保證沒有子類,防止子類繼承它,變成可變的。
將所有的屬性變成final的,保證所有的字段只能被賦值一次。

不變模式的案例
java.lang.String
所有像是修改String的操作(replace,substring等),實際上是生成了一個新的String對象
java.lang.Boolean
java.lang.Byte
java.lang.Character
java.lang.Double
java.lang.Float
java.lang.Integer
java.lang.Long
java.lang.Short
以上所有的看似改變了原來對象的操作都是生成了一個新的對象。

Future模式
核心思想是異步調用
被集成在可jdk的開發包中,核心思想就是異步調用。
技術分享圖片

技術分享圖片

如上圖更加清楚的闡述了這一過程,futuredate和realdata都繼承自Data接口,函數調用的時候返回Data接口,而不管究竟是futuredate還是realdata,將futuredate(類似上面的訂單,只是一個空殼)迅速的返回,然後等真正的數據構造完成之後再返回realdata,並且在futuredate中具有realdata的參數,來判斷時候已經將真實的數據返回。

舉個栗子
public interface Data {
public String getResult ();
}
1
2
3
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()
}
public synchronized String getResult() {//會等待RealData構造完成
while (!isReady) {
try {
wait();//一直等待,知道RealData被註入
} catch (InterruptedException e) {
}
}
return realdata.result;//由RealData實現
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class RealData implements Data {
protected final String result;
public RealData(String para) {
//RealData的構造可能很慢,需要用戶等待很久,這裏使用sleep模擬
StringBuffer sb=new StringBuffer(); for (int i = 0; i < 10; i++) {
sb.append(para);
try {
//這裏使用sleep,代替一個很慢的操作過程 Thread.sleep(100);
} catch (InterruptedException e) {
}
}
result =sb.toString();
}
public String getResult() {
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {
public Data request(final String queryStr) {
final FutureData future = new FutureData();
new Thread() {
public void run() {// RealData的構建很慢,
//所以在單獨的線程中進行
RealData realdata = new RealData(queryStr); future.setRealData(realdata);
}
}.start();
return future; // FutureData會被立即返回
}
}
1
2
3
4
5
6
7
8
9
10
11
12
重新開啟一個線程進行setRealData,但是立即返回future,供使用。

public static void main(String[] args) {
Client client = new Client(); //這裏會立即返回,因為得到的是FutureData而不是RealData
Data data = client.request("name");
System.out.println("請求完畢");
try {
//這裏可以用一個sleep代替了對其他業務邏輯的處理 //在處理這些業務邏輯的過程中,RealData被創建,從而充分利用了等待時間
Thread.sleep(2000);
} catch (InterruptedException e) {
}
//使用真實的數據
System.out.println("數據 = " + data.getResult());
}
1
2
3
如果剛執行了Data data = client.request(“name”)返回的並不是真正的數據,這個時候去getResult一定會出現阻塞,但是中間執行了sleep(2000)或做一些其他的事情,並不會影響其他的業務,在真正需要數據的時候,在getResult,能夠瞬間返回真正需要的數據。

JDK對Future模式的支持

技術分享圖片

核心是FutureTask,一個帶有Future功能的Runnable

通過callable實現future
這裏通過implements Callable來實現future的功能。

public class RealData implements Callable<String> {
private String para;
public RealData(String para){
this.para=para;
}
@Override
public String call() throws Exception {
StringBuffer sb=new StringBuffer();
for (int i = 0; i < 10; i++) {
sb.append(para);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
return sb.toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FutureMain {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//構造FutureTask
FutureTask<String> future = new FutureTask<String>(new RealData("a"));
ExecutorService executor = Executors.newFixedThreadPool(1);
//執行FutureTask,相當於上例中的 client.request("a") 發送請求
//在這裏開啟線程進行RealData的call()執行
executor.submit(future);
System.out.println("請求完畢");
try{ //這裏依然可以做額外的數據操作,這裏使用sleep代替其他業務邏輯的處理
Thread.sleep(2000);
} catch (InterruptedException e) {
}
//相當於data.getResult (),取得call()方法的返回值
//如果此時call()方法沒有執行完成,則依然會等待
System.out.println("數據 = " + future.get());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
核心代碼:
FutureTask future = new FutureTask(new RealData(“a”));
上面說到jdk中對future的支持,其核心就是FutureTask,這裏構造FutureTask
executor.submit(future);
System.out.println("數據 = " + future.get());
這裏如果,在submit和get之間沒有其他的操作直接進行get還是會形成阻塞的。

更加簡便的方式實現future
public class FutureMain2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(1);
//執行FutureTask,相當於上例中的 client.request("a") 發送請求
//在這裏開啟線程進行RealData的call()執行
Future<String> future=executor.submit(new RealData("a"));
System.out.println("請求完畢");
try {
//這裏依然可以做額外的數據操作,這裏使用sleep代替其他業務邏輯的處理
Thread.sleep(2000);
} catch (InterruptedException e) {
}
//相當於data.getResult (),取得call()方法的返回值
//如果此時call()方法沒有執行完成,則依然會等待
System.out.println("數據 = " + future.get());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
核心代碼:
Future future=executor.submit(new RealData(“a”));
System.out.println("數據 = " + future.get());
因為callable是能夠有返回值的,所以能夠直接得到future,進一步簡化了操作。

生產者消費者
生產者-消費者模式是一個經典的多線程設計模式。它為多線程間的協作提供了良好的解決方案。 在生產者-消費者模式中,通常由兩類線程,即若幹個生產者線程和若幹個消費者線程。生產者線 程負責提交用戶請求,消費者線程則負責具體處理生產者提交的任務。生產者和消費者之間則通 過共享內存緩沖區進行通信。

線程a需要知道線程b的存在,線程b需要知道線程a的存在,如果更換了名字呢?從軟件工程的角度講,一個模塊,對外最好是被知道的越少越好,一無所知最好,意味著外部的程序不論怎麽改,對我都是沒有影響的,能很好的降低耦合性。
技術分享圖片

角色 作用
生產者 用於提交用戶請求,提取用戶任務,並裝入內存緩沖區
消費者 在內存緩沖區中提取並處理任務
內存緩沖區 緩存生產者提交的任務或數據,供消費者使用
任務 生成者向內存緩沖區提交的數據結構。
Main 使用生產者和消費者的客戶端
簡單代碼實現
while (isRunning) {
Thread.sleep(r.nextInt(SLEEPTIME));
data = new PCData(count.incrementAndGet()); //構造任務數據
System.out.println(data+" is put into queue");
if (!queue.offer(data, 2, TimeUnit.SECONDS)) {
//提交數據到緩沖區中
}
}
System.err.println("failed to put data:" + data);

while(true){
PCData data = queue.take();
//提取任務
if (null != data) {
int re = data.getData() * data.getData(); //計算平方
System.out.println(MessageFormat.format("{0}*{1}={2}",
data.getData(), re));
}
}

Java高並發程序設計學習筆記(七):並行設計模式