Java 高并發(fā)七:并發(fā)設(shè)計(jì)模型詳解
1. 什么是設(shè)計(jì)模式
在軟件工程中,設(shè)計(jì)模式(design pattern)是對(duì)軟件設(shè)計(jì)中普遍存在(反復(fù)出現(xiàn))的各種問題 ,所提出的解決方案。這個(gè)術(shù)語是由埃里希·伽瑪(Erich Gamma)等人在1990年代從建筑設(shè)計(jì)領(lǐng) 域引入到計(jì)算機(jī)科學(xué)的。
著名的4人幫: Erich Gamma,Richard Helm, Ralph Johnson ,John Vlissides (Gof)
《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》收錄23種模式
2. 單例模式
單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在。許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)的全局對(duì)象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為
比如:全局信息配置
單例模式最簡單的實(shí)現(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來確定唯一性。
缺點(diǎn):何時(shí)產(chǎn)生實(shí)例 不好控制
雖然我們知道,在類Singleton第一次被加載的時(shí)候,就產(chǎn)生了一個(gè)實(shí)例。
但是如果這個(gè)類中有其他屬性
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;
}
}
當(dāng)使用
System.out.println(Singleton.STATUS);
這個(gè)實(shí)例就被產(chǎn)生了。也許此時(shí)你并不希望產(chǎn)生這個(gè)實(shí)例。
如果系統(tǒng)特別在意這個(gè)問題,這種單例的實(shí)現(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()方式時(shí)被創(chuàng)建,并且通過synchronized來確保線程安全。
這樣就控制了何時(shí)創(chuàng)建實(shí)例。
這種方法是延遲加載的典型。
但是有一個(gè)問題就是,在高并發(fā)的場景下性能會(huì)有影響,雖然只有一個(gè)判斷就return了,但是在并發(fā)量很高的情況下,或多或少都會(huì)有點(diǎn)影響,因?yàn)槎家ツ胹ynchronized的鎖。
為了高效,有了第三種方式:
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;
}
}
由于加載一個(gè)類時(shí),其內(nèi)部類不會(huì)被加載。這樣保證了只有調(diào)用getInstance()時(shí)才會(huì)產(chǎn)生實(shí)例,控制了生成實(shí)例的時(shí)間,實(shí)現(xiàn)了延遲加載。
并且去掉了synchronized,讓性能更優(yōu),用static來確保唯一性。
3. 不變模式
一個(gè)類的內(nèi)部狀態(tài)創(chuàng)建后,在整個(gè)生命期間都不會(huì)發(fā)生變化時(shí),就是不變類
不變模式不需要同步
創(chuàng)建一個(gè)不變的類:
public final class Product {
// 確保無子類
private final String no;
// 私有屬性,不會(huì)被其他對(duì)象獲取
private final String name;
// final保證屬性不會(huì)被2次賦值
private final double price;
public Product(String no, String name, double price) {
// 在創(chuàng)建對(duì)象時(shí),必須指定數(shù)據(jù)
super();
// 因?yàn)閯?chuàng)建之后,無法進(jìn)行修改
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ù)還沒完成,所以返回的是一個(gè)空的。
但是這個(gè)返回類似于購物中的訂單,將來可以根據(jù)這個(gè)訂單來得到一個(gè)結(jié)果。
所以這個(gè)Future模式意思就是,“未來”可以得到,就是指這個(gè)訂單或者說是契約,“承諾”未來就會(huì)給結(jié)果。
Future模式簡單的實(shí)現(xiàn):

調(diào)用者得到的是一個(gè)Data,一開始可能是一個(gè)FutureData,因?yàn)镽ealData構(gòu)建很慢。在未來的某個(gè)時(shí)間,可以通過FutureData來得到RealData。
代碼實(shí)現(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()//會(huì)等待RealData構(gòu)造完成
{
while (!isReady) {
try {
wait(); //一直等待,知道RealData被注入
} catch (InterruptedException e) {
}
}
return realdata.result; //由RealData實(shí)現(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,代替一個(gè)很慢的操作過程
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)建很慢,
//所以在單獨(dú)的線程中進(jìn)行
RealData realdata = new RealData(queryStr);
future.setRealData(realdata);
}
}.start();
return future; // FutureData會(huì)被立即返回
}
}
public static void main(String[] args) {
Client client = new Client();
// 這里會(huì)立即返回,因?yàn)榈玫降氖荈utureData而不是RealData
Data data = client.request("name");
System.out.println("請(qǐng)求完畢");
try {
// 這里可以用一個(gè)sleep代替了對(duì)其他業(yè)務(wù)邏輯的處理
// 在處理這些業(yè)務(wù)邏輯的過程中,RealData被創(chuàng)建,從而充分利用了等待時(shí)間
Thread.sleep(2000);
} catch (InterruptedException e) {
}
// 使用真實(shí)的數(shù)據(jù)
System.out.println("數(shù)據(jù) = " + data.getResult());
}
JDK中也有多Future模式的支持:

接下來使用JDK提供的類和方法來實(shí)現(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,相當(dāng)于上例中的 client.request("a") 發(fā)送請(qǐng)求
// 在這里開啟線程進(jìn)行RealData的call()執(zhí)行
executor.submit(future);
System.out.println("請(qǐng)求完畢");
try {
// 這里依然可以做額外的數(shù)據(jù)操作,這里使用sleep代替其他業(yè)務(wù)邏輯的處理
Thread.sleep(2000);
} catch (InterruptedException e) {
}
// 相當(dāng)于data.getResult (),取得call()方法的返回值
// 如果此時(shí)call()方法沒有執(zhí)行完成,則依然會(huì)等待
System.out.println("數(shù)據(jù) = " + future.get());
}
}
這里要注意的是FutureTask是即具有 Future功能又具有Runnable功能的類。所以又可以運(yùn)行,最后還能get。
當(dāng)然如果在調(diào)用到future.get()時(shí),真實(shí)數(shù)據(jù)還沒準(zhǔn)備好,仍然會(huì)產(chǎn)生阻塞狀況,直到數(shù)據(jù)準(zhǔn)備完成。
當(dāng)然還有更加簡便的方式:
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,相當(dāng)于上例中的 client.request("a") 發(fā)送請(qǐng)求
// 在這里開啟線程進(jìn)行RealData的call()執(zhí)行
Future<String> future = executor.submit(new RealData("a"));
System.out.println("請(qǐng)求完畢");
try {
// 這里依然可以做額外的數(shù)據(jù)操作,這里使用sleep代替其他業(yè)務(wù)邏輯的處理
Thread.sleep(2000);
} catch (InterruptedException e) {
}
// 相當(dāng)于data.getResult (),取得call()方法的返回值
// 如果此時(shí)call()方法沒有執(zhí)行完成,則依然會(huì)等待
System.out.println("數(shù)據(jù) = " + future.get());
}
}
由于Callable是有返回值的,可以直接返回future對(duì)象。
5. 生產(chǎn)者消費(fèi)者
生產(chǎn)者-消費(fèi)者模式是一個(gè)經(jīng)典的多線程設(shè)計(jì)模式。它為多線程間的協(xié)作提供了良好的解決方案。 在生產(chǎn)者-消費(fèi)者模式中,通常由兩類線程,即若干個(gè)生產(chǎn)者線程和若干個(gè)消費(fèi)者線程。生產(chǎn)者線 程負(fù)責(zé)提交用戶請(qǐng)求,消費(fèi)者線程則負(fù)責(zé)具體處理生產(chǎn)者提交的任務(wù)。生產(chǎn)者和消費(fèi)者之間則通 過共享內(nèi)存緩沖區(qū)進(jìn)行通信。
以前寫過一篇用Java來實(shí)現(xiàn)生產(chǎn)者消費(fèi)者的多種方法,這里就不多闡述了。
相關(guān)文章
Java調(diào)用shell命令涉及管道、重定向時(shí)不生效問題及解決
這篇文章主要介紹了Java調(diào)用shell命令涉及管道、重定向時(shí)不生效問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
springboot themaleaf 第一次進(jìn)頁面不加載css的問題
這篇文章主要介紹了springboot themaleaf 第一次進(jìn)頁面不加載css的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
SpringCloud之服務(wù)注冊(cè)與發(fā)現(xiàn)Spring Cloud Eureka實(shí)例代碼
這篇文章主要介紹了SpringCloud之服務(wù)注冊(cè)與發(fā)現(xiàn)Spring Cloud Eureka實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04
Java/Android 獲取網(wǎng)絡(luò)重定向文件的真實(shí)URL的示例代碼
本篇文章主要介紹了Java/Android 獲取網(wǎng)絡(luò)重定向文件的真實(shí)URL的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
Springboot如何使用Map將錯(cuò)誤提示輸出到頁面
這篇文章主要介紹了Springboot如何使用Map將錯(cuò)誤提示輸出到頁面,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
Java實(shí)體類實(shí)現(xiàn)鏈?zhǔn)讲僮鲗?shí)例解析
這篇文章主要介紹了Java實(shí)體類實(shí)現(xiàn)鏈?zhǔn)讲僮鲗?shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Java 如何將表格數(shù)據(jù)導(dǎo)入word文檔中
這篇文章主要介紹了Java將表格數(shù)據(jù)導(dǎo)入word文檔中的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06

