Java高並發程序設計學習筆記(七):並行設計模式
轉自:https://blog.csdn.net/dataiyangu/article/details/87123586
什麽是設計模式
架構模式
設計模式
代碼模式(成例 Idiom)
單例模式
普通單例
假如單例中有某個字段
改進的單例
代理模式再升級
不變模式
不變模式是如何實現的
不變模式的案例
Future模式
核心思想是異步調用
舉個栗子
JDK對Future模式的支持
通過callable實現future
更加簡便的方式實現future
生產者消費者
簡單代碼實現
什麽是設計模式
在軟件工程中,設計模式(design pattern)是對軟件設計中普遍存在(反復出現)的各種問題 ,所提出的解決方案。這個術語是由埃裏希·伽瑪(Erich Gamma)等人在1990年代從建築設計領 域引入到計算機科學的。
《設計模式:可復用面向對象軟件的基礎》 收錄 23種模式
– 觀察者模式
– 策略模式
– 裝飾者模式
– 享元模式
– 模板方法
– …
架構模式
– MVC
– 分層
設計模式
– 提煉系統中的組件
代碼模式(成例 Idiom)
– 低層次,與編碼直接相關
– 如DCL
class Person {
String name;
int birthYear;
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高並發程序設計學習筆記(七):並行設計模式