1. 什麼是設計模式
在軟件工程中,設計模式(design pattern)是對軟件設計中普遍存在(反覆出現)的各種問題 ,所提出的解決方案。這個術語是由埃裏希·伽瑪(Erich Gamma)等人在1990年代從建築設計領 域引入到計算機科學的。
著名的4人幫: Erich Gamma,Richard Helm, Ralph Johnson ,John Vlissides (Gof)
《設計模式:可複用面向對象軟件的基礎》收錄23種模式
2. 單例模式
單例對象的類必須保證只有一個實例存在。許多時候整個系統只需要擁有一個的全局對象,這樣有利於我們協調系統整體的行爲
比如:全局信息配置
單例模式最簡單的實現:
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第一次被加載的時候,就產生了一個實例。
但是如果這個類中有其他屬性
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. 生產者消費者
生產者-消費者模式是一個經典的多線程設計模式。它爲多線程間的協作提供了良好的解決方案。 在生產者-消費者模式中,通常由兩類線程,即若干個生產者線程和若干個消費者線程。生產者線 程負責提交用戶請求,消費者線程則負責具體處理生產者提交的任務。生產者和消費者之間則通 過共享內存緩衝區進行通信。