Java設(shè)計(jì)模式-單例模式
Java常用設(shè)計(jì)模式-單例模式
Java Design Patterns:
創(chuàng)建型模式:工廠方法、抽象方法、建造者、原型、單例
結(jié)構(gòu)型模式有:適配器、橋接、組合、裝飾器、外觀、享元、代理
行為型模式有:責(zé)任鏈、命令、解釋器、迭代器、中介、備忘錄、觀察者、狀態(tài)、策略、模板方法、訪問者
常用設(shè)計(jì)模式:
單例模式、工廠模式、代理模式、策略模式&模板模式、門面模式、責(zé)任鏈模式、裝飾器模式、組合模式、builder模式
單例模式
簡介
確保一個類只有一個實(shí)例,并提供一個全局訪問點(diǎn)
懶漢式:
/**
* 單例設(shè)計(jì)模式:確保一個類只有一個對象實(shí)例,并提供一個全局訪問點(diǎn)
* 懶漢式:
* 是否 Lazy 初始化:是
* 是否多線程安全:否
*/
public class Signleton {
private static Signleton signleton;
private Signleton(){}
//可以通過synchronized關(guān)鍵字保證線程安全
public static Signleton getSignleton(){
if(signleton == null){
signleton = new Signleton();
}
return signleton;
}
}
餓漢式:
/**
* 單例設(shè)計(jì)模式:確保一個類只有一個對象實(shí)例,并提供一個全局訪問點(diǎn)
* 餓漢式:
* 是否 Lazy 初始化:否
* 是否多線程安全:是
*/
class Signleton1{
private static Signleton1 signleton1 = new Signleton1();
private Signleton1(){}
public static Signleton1 getSignleton1(){
return signleton1;
}
}
懶漢式:解決反射、序列化反序列化問題
/**
* 單例設(shè)計(jì)模式:確保一個類只有一個對象實(shí)例,并提供一個全局訪問點(diǎn)
* 懶漢式:
* 是否 Lazy 初始化:是
* 是否多線程安全:否
*/
public class Signleton implements Serializable {
private static final long serialVersionUID = 1L;
private static Signleton signleton;
private Signleton() {
// 防止反射
if (signleton != null) {
throw new RuntimeException();
}
}
// 可以通過synchronized關(guān)鍵字保證線程安全
public static Signleton getSignleton() {
if (signleton == null) {
signleton = new Signleton();
}
return signleton;
}
/*
序列化:當(dāng)一個對象被序列化時,Java 將該對象的狀態(tài)寫入一個字節(jié)流。
反序列化:當(dāng)字節(jié)流被反序列化時,Java 將創(chuàng)建一個新的對象實(shí)例,并將字節(jié)流中的數(shù)據(jù)填充到這個新實(shí)例中。
readResolve 方法:在對象被反序列化之后,Java 會調(diào)用這個方法。如果該方法存在,返回的對象將代替默認(rèn)反序列化過程中創(chuàng)建的新對象。
*/
private Object readResolve() {
return signleton;
}
}
/**
* 反射測試
*/
@Test
public void test(){
//獲取單例
Signleton signleton = Signleton.getSignleton();
Signleton signleton1 = Signleton.getSignleton();
System.out.println(signleton.hashCode());
System.out.println(signleton1.hashCode());
//通過反射破壞單例
try {
Class<?> aClass = Class.forName("design.patterns.Signleton");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Signleton signleton2 = (Signleton) declaredConstructor.newInstance();
System.out.println(signleton2.hashCode());
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* 序列化測試
*/
@Test
public void test1(){
//獲取單例
Signleton signleton = Signleton.getSignleton();
Signleton signleton1 = Signleton.getSignleton();
System.out.println(signleton.hashCode());
System.out.println(signleton1.hashCode());
//序列化反序列化獲取對象
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("D:/signleton.ser"));
outputStream.writeObject(signleton1);
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("D:/signleton.ser"));
Signleton signleton2 = (Signleton) inputStream.readObject();
System.out.println(signleton2.hashCode());
} catch (IOException | ClassNotFoundException e) {
// throw new RuntimeException(e);
e.printStackTrace();
}
}
懶漢式DCL(推薦):雙重檢查鎖定(Double-Checked Locking)是用于減少同步開銷,同時保證線程安全的一種優(yōu)化方法。其核心思想是:在訪問共享資源時,先進(jìn)行一次非同步的檢查,如果未初始化,再進(jìn)入同步塊進(jìn)行第二次檢查和初始化。這樣可以避免每次調(diào)用獲取實(shí)例方法時都需要進(jìn)行同步,從而提升性能。
這里也確保序列化安全。
/**
* 單例設(shè)計(jì)模式:確保一個類只有一個對象實(shí)例,并提供一個全局訪問點(diǎn)
* 懶漢式:
* 是否 Lazy 初始化:是
* 是否多線程安全:否
*/
public class Signleton implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Signleton signleton;
private Signleton() {}
public static Signleton getSignleton() {
if (signleton == null) {
synchronized(Signleton.class){
if(signleton == null){
signleton = new Signleton();
}
}
}
return signleton;
}
/*
序列化:當(dāng)一個對象被序列化時,Java 將該對象的狀態(tài)寫入一個字節(jié)流。
反序列化:當(dāng)字節(jié)流被反序列化時,Java 將創(chuàng)建一個新的對象實(shí)例,并將字節(jié)流中的數(shù)據(jù)填充到這個新實(shí)例中。
readResolve 方法:在對象被反序列化之后,Java 會調(diào)用這個方法。如果該方法存在,返回的對象將代替默認(rèn)反序列化過程中創(chuàng)建的新對象。
*/
private Object readResolve() {
return signleton;
}
}
場景
資源共享:避免頻繁的創(chuàng)建銷毀某個對象,造成存好。比如:日志文件。
控制資源:避免過多的對象產(chǎn)生,造成其他問題。比如網(wǎng)站的計(jì)數(shù)器。
應(yīng)用場景:
-
日志管理器:避免頻繁創(chuàng)建和銷毀日志對象,確保日志文件只被一個實(shí)例操作,以便內(nèi)容可以正確追加。
-
網(wǎng)站計(jì)數(shù)器:全局唯一實(shí)例用于統(tǒng)計(jì)網(wǎng)站訪問次數(shù),避免并發(fā)更新問題。
-
Windows 回收站:整個系統(tǒng)運(yùn)行過程中,回收站一直維護(hù)著唯一的一個實(shí)例。
-
多線程的線程池:線程池需要方便控制池中的線程,單例模式確保線程池全局唯一。
-
/** * 單例線程池 -- 應(yīng)用場景 */ public class ThreadPool1 { private static ThreadPool1 threadPool; // 定義接口 private ExecutorService executorService; private ThreadPool1() { executorService = new ThreadPoolExecutor( 5, // 核心線程數(shù) 10, // 總線程數(shù) 60, TimeUnit.MILLISECONDS, // 存活時間和單位 new LinkedBlockingDeque<Runnable>(), // 用于保存等待執(zhí)行的任務(wù)的隊(duì)列 new ThreadFactory() { // 用于創(chuàng)建新線程的工廠 // 定義原子操作的 int 類型。它可以在多線程環(huán)境下安全地進(jìn)行自增、自減等操作而不需要同步 private AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, "CustomThreadPool-thread-" + threadNumber.getAndIncrement()); // thread.setDaemon(true); // 設(shè)置為守護(hù)線程 thread.setPriority(Thread.NORM_PRIORITY); return thread; } }, new ThreadPoolExecutor.CallerRunsPolicy() // 拒絕策略,當(dāng)任務(wù)隊(duì)列滿了且無法再接受任務(wù)時的處理策略 ); } public static synchronized ThreadPool1 getThreadPool() { if (threadPool == null) { threadPool = new ThreadPool1(); } return threadPool; } public void submitTask(Runnable runnable) { executorService.submit(runnable); } public void shutdown() { executorService.shutdown(); } } /** * 單例線程池測試 */ @Test public void test2(){ Runnable runnable = () -> { System.out.println(Thread.currentThread().getName() + " task is run"); }; // ThreadPool1.getThreadPool().submitTask(runnable); ThreadPool1 threadPool = ThreadPool1.getThreadPool(); threadPool.submitTask(runnable); System.out.println(threadPool.hashCode()); ThreadPool1 threadPool1 = ThreadPool1.getThreadPool(); threadPool1.submitTask(runnable); System.out.println(threadPool1.hashCode()); } //輸出: 158199555 158199555 CustomThreadPool-thread-2 task is run CustomThreadPool-thread-1 task is run
-
-
SpringBoot中的大多數(shù)容器管理的Bean都是單例的,這些bean是應(yīng)用程序級別的單例,也就是說不同用戶共享同一個實(shí)例。比如@RestController、@Service、@Compoment、@Configuration注解修飾的類,默認(rèn)都是單例。
優(yōu)點(diǎn)
控制資源的使用:
- 實(shí)例控制:確保一個類只有一個實(shí)例,避免了多個實(shí)例導(dǎo)致的資源浪費(fèi)。例如,在數(shù)據(jù)庫連接池或線程池的設(shè)計(jì)中,單例模式確保只創(chuàng)建一個連接池或線程池實(shí)例,從而控制資源的使用。
- 節(jié)省資源:減少了系統(tǒng)的開銷,避免了重復(fù)創(chuàng)建和銷毀對象的高昂成本。
全局訪問點(diǎn):
- 統(tǒng)一管理:通過提供一個全局訪問點(diǎn),可以方便地管理和訪問實(shí)例。比如,在日志記錄系統(tǒng)中,通過單例模式可以確保所有日志記錄都通過同一個實(shí)例進(jìn)行處理,從而統(tǒng)一日志的輸出格式和內(nèi)容。
- 一致性:所有對該實(shí)例的操作都通過統(tǒng)一的接口進(jìn)行,確保了數(shù)據(jù)的一致性和完整性。
易于擴(kuò)展:
- 延遲實(shí)例化:懶漢式單例模式在首次使用時才創(chuàng)建實(shí)例,避免了不必要的資源浪費(fèi)。這種延遲加載的特性也便于在系統(tǒng)啟動時減少初始化時間。
缺點(diǎn)
潛在的資源爭用:
- 資源競爭:如果單例類內(nèi)部使用了共享資源,而這些資源在高并發(fā)場景下沒有妥善處理,可能導(dǎo)致資源競爭問題。例如,某個單例實(shí)例持有數(shù)據(jù)庫連接對象,在高并發(fā)請求下可能導(dǎo)致連接池枯竭。
單點(diǎn)故障:
- 故障影響范圍大:如果單例實(shí)例出現(xiàn)問題,整個系統(tǒng)的相關(guān)功能可能會受到影響。例如,日志記錄系統(tǒng)的單例實(shí)例出現(xiàn)異常,可能導(dǎo)致整個系統(tǒng)無法正常記錄日志。
難以測試:
- 測試復(fù)雜性:由于單例模式在整個應(yīng)用程序中只有一個實(shí)例,單元測試時可能會導(dǎo)致測試的隔離性和獨(dú)立性變差。例如,一個單例類的狀態(tài)在多個測試方法之間共享,可能導(dǎo)致測試結(jié)果互相影響。
隱藏依賴關(guān)系:
- 依賴性不明確:單例模式通過全局訪問點(diǎn)訪問實(shí)例,可能會導(dǎo)致類與類之間的依賴關(guān)系變得不清晰。例如,一個類可能隱式依賴于某個單例類的狀態(tài),增加了系統(tǒng)的復(fù)雜性和維護(hù)成本。
本文來自博客園,作者:Liberty碼農(nóng)志,轉(zhuǎn)載請注明原文鏈接:http://www.rzrgm.cn/zhiliu/p/18319807

浙公網(wǎng)安備 33010602011771號