Java 高并發(fā)七:并發(fā)設(shè)計模型詳解
1. 什么是設(shè)計模式
在軟件工程中,設(shè)計模式(design pattern)是對軟件設(shè)計中普遍存在(反復(fù)出現(xiàn))的各種問題 ,所提出的解決方案。這個術(shù)語是由埃里?!べが敚‥rich Gamma)等人在1990年代從建筑設(shè)計領(lǐng) 域引入到計算機科學(xué)的。
著名的4人幫: Erich Gamma,Richard Helm, Ralph Johnson ,John Vlissides (Gof)
《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》收錄23種模式
2. 單例模式
單例對象的類必須保證只有一個實例存在。許多時候整個系統(tǒng)只需要擁有一個的全局對象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為
比如:全局信息配置
單例模式最簡單的實現(xiàn):
public class Singleton { private Singleton() { System.out.println("Singleton is create"); } private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } }
由私有構(gòu)造方法和static來確定唯一性。
缺點:何時產(chǎn)生實例 不好控制
雖然我們知道,在類Singleton第一次被加載的時候,就產(chǎn)生了一個實例。
但是如果這個類中有其他屬性
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);
這個實例就被產(chǎn)生了。也許此時你并不希望產(chǎn)生這個實例。
如果系統(tǒng)特別在意這個問題,這種單例的實現(xiàn)方法就不太好。
第二種單例模式的解決方式:
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只有在調(diào)用getInstance()方式時被創(chuàng)建,并且通過synchronized來確保線程安全。
這樣就控制了何時創(chuàng)建實例。
這種方法是延遲加載的典型。
但是有一個問題就是,在高并發(fā)的場景下性能會有影響,雖然只有一個判斷就return了,但是在并發(fā)量很高的情況下,或多或少都會有點影響,因為都要去拿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; } }
由于加載一個類時,其內(nèi)部類不會被加載。這樣保證了只有調(diào)用getInstance()時才會產(chǎn)生實例,控制了生成實例的時間,實現(xiàn)了延遲加載。
并且去掉了synchronized,讓性能更優(yōu),用static來確保唯一性。
3. 不變模式
一個類的內(nèi)部狀態(tài)創(chuàng)建后,在整個生命期間都不會發(fā)生變化時,就是不變類
不變模式不需要同步
創(chuàng)建一個不變的類:
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) { // 在創(chuàng)建對象時,必須指定數(shù)據(jù) super(); // 因為創(chuàng)建之后,無法進行修改 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模式
核心思想是異步調(diào)用
非異步:
異步:
第一次的call_return由于任務(wù)還沒完成,所以返回的是一個空的。
但是這個返回類似于購物中的訂單,將來可以根據(jù)這個訂單來得到一個結(jié)果。
所以這個Future模式意思就是,“未來”可以得到,就是指這個訂單或者說是契約,“承諾”未來就會給結(jié)果。
Future模式簡單的實現(xiàn):
調(diào)用者得到的是一個Data,一開始可能是一個FutureData,因為RealData構(gòu)建很慢。在未來的某個時間,可以通過FutureData來得到RealData。
代碼實現(xiàn):
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已經(jīng)被注入,通知getResult() } public synchronized String getResult()//會等待RealData構(gòu)造完成 { while (!isReady) { try { wait(); //一直等待,知道RealData被注入 } catch (InterruptedException e) { } } return realdata.result; //由RealData實現(xiàn) } } public class RealData implements Data { protected final String result; public RealData(String para) { // RealData的構(gòu)造可能很慢,需要用戶等待很久,這里使用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的構(gòu)建很慢, //所以在單獨的線程中進行 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代替了對其他業(yè)務(wù)邏輯的處理 // 在處理這些業(yè)務(wù)邏輯的過程中,RealData被創(chuàng)建,從而充分利用了等待時間 Thread.sleep(2000); } catch (InterruptedException e) { } // 使用真實的數(shù)據(jù) System.out.println("數(shù)據(jù) = " + data.getResult()); }
JDK中也有多Future模式的支持:
接下來使用JDK提供的類和方法來實現(xiàn)剛剛的代碼:
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 { // 構(gòu)造FutureTask FutureTask<String> future = new FutureTask<String>(new RealData("a")); ExecutorService executor = Executors.newFixedThreadPool(1); // 執(zhí)行FutureTask,相當于上例中的 client.request("a") 發(fā)送請求 // 在這里開啟線程進行RealData的call()執(zhí)行 executor.submit(future); System.out.println("請求完畢"); try { // 這里依然可以做額外的數(shù)據(jù)操作,這里使用sleep代替其他業(yè)務(wù)邏輯的處理 Thread.sleep(2000); } catch (InterruptedException e) { } // 相當于data.getResult (),取得call()方法的返回值 // 如果此時call()方法沒有執(zhí)行完成,則依然會等待 System.out.println("數(shù)據(jù) = " + future.get()); } }
這里要注意的是FutureTask是即具有 Future功能又具有Runnable功能的類。所以又可以運行,最后還能get。
當然如果在調(diào)用到future.get()時,真實數(shù)據(jù)還沒準備好,仍然會產(chǎn)生阻塞狀況,直到數(shù)據(jù)準備完成。
當然還有更加簡便的方式:
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); // 執(zhí)行FutureTask,相當于上例中的 client.request("a") 發(fā)送請求 // 在這里開啟線程進行RealData的call()執(zhí)行 Future<String> future = executor.submit(new RealData("a")); System.out.println("請求完畢"); try { // 這里依然可以做額外的數(shù)據(jù)操作,這里使用sleep代替其他業(yè)務(wù)邏輯的處理 Thread.sleep(2000); } catch (InterruptedException e) { } // 相當于data.getResult (),取得call()方法的返回值 // 如果此時call()方法沒有執(zhí)行完成,則依然會等待 System.out.println("數(shù)據(jù) = " + future.get()); } }
由于Callable是有返回值的,可以直接返回future對象。
5. 生產(chǎn)者消費者
生產(chǎn)者-消費者模式是一個經(jīng)典的多線程設(shè)計模式。它為多線程間的協(xié)作提供了良好的解決方案。 在生產(chǎn)者-消費者模式中,通常由兩類線程,即若干個生產(chǎn)者線程和若干個消費者線程。生產(chǎn)者線 程負責(zé)提交用戶請求,消費者線程則負責(zé)具體處理生產(chǎn)者提交的任務(wù)。生產(chǎn)者和消費者之間則通 過共享內(nèi)存緩沖區(qū)進行通信。
以前寫過一篇用Java來實現(xiàn)生產(chǎn)者消費者的多種方法,這里就不多闡述了。
相關(guān)文章
Java調(diào)用shell命令涉及管道、重定向時不生效問題及解決
這篇文章主要介紹了Java調(diào)用shell命令涉及管道、重定向時不生效問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12springboot themaleaf 第一次進頁面不加載css的問題
這篇文章主要介紹了springboot themaleaf 第一次進頁面不加載css的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10SpringCloud之服務(wù)注冊與發(fā)現(xiàn)Spring Cloud Eureka實例代碼
這篇文章主要介紹了SpringCloud之服務(wù)注冊與發(fā)現(xiàn)Spring Cloud Eureka實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04Java/Android 獲取網(wǎng)絡(luò)重定向文件的真實URL的示例代碼
本篇文章主要介紹了Java/Android 獲取網(wǎng)絡(luò)重定向文件的真實URL的示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11Java 如何將表格數(shù)據(jù)導(dǎo)入word文檔中
這篇文章主要介紹了Java將表格數(shù)據(jù)導(dǎo)入word文檔中的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06