[高併發Java 七] 併發設計模式

1. 什麼是設計模式

在軟件工程中,設計模式(design pattern)是對軟件設計中廣泛存在(反覆出現)的各類問題 ,所提出的解決方案。這個術語是由埃裏希·伽瑪(Erich Gamma)等人在1990年代從建築設計領 域引入到計算機科學的。  java

著名的4人幫: Erich Gamma,Richard Helm, Ralph Johnson ,John Vlissides (Gof)  設計模式

《設計模式:可複用面向對象軟件的基礎》收錄23種模式  安全

2. 單例模式

單例對象的類必須保證只有一個實例存在。許多時候整個系統只須要擁有一個的全局對象,這樣有利於咱們協調系統總體的行爲  多線程

好比:全局信息配置 併發

單例模式最簡單的實現: app

public class Singleton {
	private Singleton() {
		System.out.println("Singleton is create");
	}
	private static Singleton instance = new Singleton();
	public static Singleton getInstance() {
		return instance;
	}
}
由私有構造方法和static來肯定惟一性。

缺點:什麼時候產生實例 很差控制  異步

雖然咱們知道,在類Singleton第一次被加載的時候,就產生了一個實例。 ide

可是若是這個類中有其餘屬性 高併發

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);
這個實例就被產生了。也許此時你並不但願產生這個實例。

若是系統特別在乎這個問題,這種單例的實現方法就不太好。 性能

第二種單例模式的解決方式:

public class Singleton {
	private Singleton() {
		System.out.println("Singleton is create");
	}
	private static Singleton instance = null;
	public static synchronized Singleton getInstance() {
		if (instance == null)
			instance = new Singleton();
		return instance;
	}
}
讓instance只有在調用getInstance()方式時被建立,而且經過synchronized來確保線程安全。

這樣就控制了什麼時候建立實例。

這種方法是延遲加載的典型。

可是有一個問題就是,在高併發的場景下性能會有影響,雖然只有一個判斷就return了,可是在併發量很高的狀況下,或多或少都會有點影響,由於都要去拿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;
	}
}
因爲加載一個類時,其內部類不會被加載。這樣保證了只有調用getInstance()時纔會產生實例,控制了生成實例的時間,實現了延遲加載。

而且去掉了synchronized,讓性能更優,用static來確保惟一性。

3. 不變模式

一個類的內部狀態建立後,在整個生命期間都不會發生變化時,就是不變類 

不變模式不須要同步 

建立一個不變的類:

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;
	}

}
Java中不變的模式的案例有:
  • java.lang.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  

4. Future模式

核心思想是異步調用 

非異步:

異步:

第一次的call_return因爲任務還沒完成,因此返回的是一個空的。

可是這個返回相似於購物中的訂單,未來能夠根據這個訂單來獲得一個結果。

因此這個Future模式意思就是,「將來」能夠獲得,就是指這個訂單或者說是契約,「承諾」將來就會給結果。

Future模式簡單的實現:

調用者獲得的是一個Data,一開始多是一個FutureData,由於RealData構建很慢。在將來的某個時間,能夠經過FutureData來獲得RealData。

代碼實現:

public interface Data {     
	public String getResult (); 
}
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實現       
	} 
}
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;
	}
}
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會被當即返回     
	} 
}
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());
	}
JDK中也有多Future模式的支持:

接下來使用JDK提供的類和方法來實現剛剛的代碼:

import java.util.concurrent.Callable;

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();
	}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

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());
	}
}
這裏要注意的是FutureTask是即具備 Future功能又具備Runnable功能的類。因此又能夠運行,最後還能get。

固然若是在調用到future.get()時,真實數據還沒準備好,仍然會產生阻塞情況,直到數據準備完成。

固然還有更加簡便的方式:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.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());
	}
}
因爲Callable是有返回值的,能夠直接返回future對象。

5. 生產者消費者

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

之前寫過一篇用Java來實現生產者消費者的多種方法,這裏就很少闡述了。







系列:

[高併發Java 一] 前言

[高併發Java 二] 多線程基礎

[高併發Java 三] Java內存模型和線程安全

[高併發Java 四] 無鎖

[高併發Java 五] JDK併發包1

[高併發Java 六] JDK併發包2

[高併發Java 七] 併發設計模式

[高併發Java 八] NIO和AIO

[高併發Java 九] 鎖的優化和注意事項

[高併發Java 十] JDK8對併發的新支持

相關文章
相關標籤/搜索