java面試常見模式問題---單例模式
1、簡介
單例模式使⽤場景:
- 業(yè)務(wù)系統(tǒng)全局只需要⼀個(gè)對象實(shí)例,⽐如發(fā)號器、 redis 連接對象等。
Spring IOC容器中的 Bean 默認(rèn)就是單例。- Spring Boot 中的 Controller、Service、Dao 層中通過
@Autowire的依賴注⼊對象默認(rèn)都是單例的。
單例模式分類:
- 懶漢:就是所謂的懶加載,延遲創(chuàng)建對象,需要用的時(shí)候再創(chuàng)建對象。
- 餓漢:與懶漢相反,提前創(chuàng)建對象。
- 單例模式實(shí)現(xiàn)步驟:
- 私有化構(gòu)造函數(shù)。
- 提供獲取單例的方法。
2、單例模式——懶漢式
單例模式——懶漢式有以下⼏種實(shí)現(xiàn)⽅式:
/**
* @Auther: csp1999
* @Date: 2020/11/06/20:36
* @Description: 單例設(shè)計(jì)模式-懶漢式
*/
public class SingletonLazy {
// 當(dāng)需要用到該實(shí)例的時(shí)候再創(chuàng)建實(shí)例對象
private static SingletonLazy instance;
/**
* 構(gòu)造函數(shù)私有化
* 不能通過 new SingletonLazy() 的方式創(chuàng)建實(shí)例
*
* 當(dāng)需要用到該實(shí)例的時(shí)候在加載
* 只能通過 SingletonLazy.getInstance() 這種方式獲取實(shí)例
*/
private SingletonLazy() {
}
/**
* 單例對象的方法
*/
public void process() {
System.out.println("方法實(shí)例化成功!");
}
/**
* 方式一:
* <p>
* 對外暴露一個(gè)方法獲取該類的對象
* <p>
* 缺點(diǎn):線程不安全,多線程下存在安全問題
*
* @return
*/
public static SingletonLazy getInstance() {
if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建
/**
* 線程安全問題:
* 當(dāng)某一時(shí)刻,兩個(gè)或多個(gè)線程同時(shí)判斷到instance == null成立的時(shí)候
* 這些線程同時(shí)進(jìn)入該if判斷內(nèi)部執(zhí)行實(shí)例化
* 則會新建出不止一個(gè)SingletonLazy實(shí)例
*/
instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對象
}
return instance;
}
/**
* 方式二:
* 通過加synchronized鎖 保證線程安全
*
* 采用synchronized 對方法加鎖有很大的性能開銷
* 因?yàn)楫?dāng)getInstance2()內(nèi)部邏輯比較復(fù)雜的時(shí)候,在高并發(fā)條件下
* 沒獲取到加鎖方法執(zhí)行權(quán)的線程,都得等到這個(gè)方法內(nèi)的復(fù)雜邏輯執(zhí)行完后才能執(zhí)行,等待浪費(fèi)時(shí)間,效率比較低
*
* @return
*/
public static synchronized SingletonLazy getInstance2() {
if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建
// 方法上加synchronized鎖后可以保證線程安全
instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對象
}
return instance;
}
/**
* 方式三:
* 在getInstance3()方法內(nèi),針對局部需要加鎖的代碼塊加鎖,而不是給整個(gè)方法加鎖
*
* 也存在缺陷:
* @return
*/
public static SingletonLazy getInstance3() {
if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建
// 局部加鎖后可以保證線程安全,效率較高
// 缺陷:假設(shè)線程A和線程B
synchronized (SingletonLazy.class){
// 當(dāng)線程A獲得鎖的執(zhí)行權(quán)的時(shí)候B等待 A執(zhí)行new SingletonLazy();實(shí)例化
// 當(dāng)A線程執(zhí)行完畢后,B再獲得執(zhí)行權(quán),這時(shí)候還是可以實(shí)例化該對象
instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對象
}
}
return instance;
}
}
單例模式:懶漢實(shí)現(xiàn) + 雙重檢查鎖定 + 內(nèi)存模型
對于上面方式三存在的缺陷,我們可以使用雙重檢查鎖定的方式對其進(jìn)行改進(jìn):
/**
* 方式三改進(jìn)版本:
* 在getInstance3()方法內(nèi),針對局部需要加鎖的代碼塊加鎖,而不是給整個(gè)方法加鎖
*
* DCL 雙重檢查鎖定 (Double-Checked-Locking) 在多線程情況下保持高性能
*
* 這是否安全? instance = new SingletonLazy(); 并不是原子性操作
* jvm中 instance實(shí)例化內(nèi)存模型流程如下:
* 1.分配空間給對象
* 2.在空間內(nèi)創(chuàng)建對象
* 3.將對象賦值給instance引用
*
* 假如出現(xiàn)如下順序錯(cuò)亂的情況:
* 線程的執(zhí)行順序?yàn)椋? -> 3 -> 2, 那么這時(shí)候會把值寫回主內(nèi)存
* 則,其他線程就會讀取到instance的最新值,但是這個(gè)是不完全的對象
* (指令重排現(xiàn)象)
*
* @return
*/
public static SingletonLazy getInstance3plus() {
if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建
// 局部加鎖后可以保證線程安全,效率較高
// 假設(shè)線程A和線程B
synchronized (SingletonLazy.class){// 第一重檢查
// 當(dāng)線程A獲得鎖的執(zhí)行權(quán)的時(shí)候B等待 A執(zhí)行new SingletonLazy();實(shí)例化
// 當(dāng)A線程執(zhí)行完畢后,B再獲得執(zhí)行權(quán),這時(shí)候再判斷instance == null是否成立
// 如果不成立,B線程無法 實(shí)例化SingletonLazy
if (instance == null){// 第二重檢查
instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對象
}
}
}
return instance;
}
再次升級方式三,來解決內(nèi)存模型中的指令重排問題:
// 添加volatile 關(guān)鍵字,禁止實(shí)例化對象時(shí),內(nèi)存模型中出現(xiàn)指令重排現(xiàn)象
private static volatile SingletonLazy instance;
/**
* 方式三再次升級版本:
* 在getInstance3()方法內(nèi),針對局部需要加鎖的代碼塊加鎖,而不是給整個(gè)方法加鎖
*
* DCL 雙重檢查鎖定 (Double-Checked-Locking) 在多線程情況下保持高性能
*
* 解決指令重排問題——禁止指令重排
* @return
*/
public static SingletonLazy getInstance3plusplus() {
if (instance == null) {// 實(shí)例為null時(shí)候才創(chuàng)建
// 局部加鎖后可以保證線程安全,效率較高
// 假設(shè)線程A和線程B
synchronized (SingletonLazy.class){// 第一重檢查
// 當(dāng)線程A獲得鎖的執(zhí)行權(quán)的時(shí)候B等待 A執(zhí)行new SingletonLazy();實(shí)例化
// 當(dāng)A線程執(zhí)行完畢后,B再獲得執(zhí)行權(quán),這時(shí)候再判斷instance == null是否成立
// 如果不成立,B線程無法 實(shí)例化SingletonLazy
if (instance == null){// 第二重檢查
instance = new SingletonLazy();// 當(dāng)需要的時(shí)候再進(jìn)行實(shí)例化對象
}
}
}
return instance;
}
單例模式——懶漢式調(diào)用:
@Test
public void testSingletonLazy(){
SingletonLazy.getInstance().process();
}
3、單例模式——餓漢式
/**
* @Auther: csp1999
* @Date: 2020/11/06/21:39
* @Description: 單例設(shè)計(jì)模式-餓漢式
*/
public class SingletonHungry {
// 當(dāng)類加載的時(shí)候就直接實(shí)例化對象
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry(){}
/**
* 單例對象的方法
*/
public void process() {
System.out.println("方法實(shí)例化成功!");
}
public static SingletonHungry getInstance(){
return instance;// 當(dāng)類加載的時(shí)候就直接實(shí)例化對象
}
}
單例模式——餓漢式調(diào)用:
@Test
public void testSingletonHungry(){
SingletonHungry.getInstance().process();
}
餓漢式單例模式,當(dāng)類加載的時(shí)候就直接實(shí)例化對象,因此不需要考慮線程安全問題。
- 優(yōu)點(diǎn):實(shí)現(xiàn)簡單,不需要考慮線程安全問題。
- 缺點(diǎn):不管有沒有使用該對象實(shí)例,instance對象一直占用著這段內(nèi)存。
懶漢與餓漢式如何選擇?
- 如果對象內(nèi)存占用不大,且創(chuàng)建不復(fù)雜,直接使用餓漢的方式即可。
- 其他情況均采用懶漢方式(優(yōu)選)。
總結(jié)
文章會不定時(shí)更新,有時(shí)候一天多更新幾篇,如果幫助您復(fù)習(xí)鞏固了知識點(diǎn),還請支持一下,后續(xù)會億點(diǎn)點(diǎn)的更新!希望大家多多關(guān)注腳本之家的其他內(nèi)容!
相關(guān)文章
MyBatis實(shí)現(xiàn)樂觀鎖和悲觀鎖的示例代碼
在數(shù)據(jù)庫操作中,樂觀鎖和悲觀鎖是兩種常見的并發(fā)控制策略,本文主要介紹了MyBatis實(shí)現(xiàn)樂觀鎖和悲觀鎖的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07
SpringBoot的HTTPS配置實(shí)現(xiàn)
本文主要介紹了SpringBoot的HTTPS配置實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
springboot 2.x整合mybatis實(shí)現(xiàn)增刪查和批量處理方式
這篇文章主要介紹了springboot 2.x整合mybatis實(shí)現(xiàn)增刪查和批量處理方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
Java服務(wù)中的大文件上傳和下載優(yōu)化技巧分享
在Java服務(wù)中處理大文件的上傳和下載是一項(xiàng)常見但復(fù)雜的任務(wù),為了提供優(yōu)秀的用戶體驗(yàn)和高效的系統(tǒng)性能,我們將探索多種策略和技術(shù),并在每一點(diǎn)上都提供代碼示例以便實(shí)戰(zhàn)應(yīng)用,需要的朋友可以參考下2023-10-10
Springboot項(xiàng)目編寫測試單元完整步驟記錄
這篇文章主要介紹了如何使用JUnit編寫Spring?Boot項(xiàng)目中的測試單元,包括引入依賴、配置文件設(shè)置、啟動(dòng)文件創(chuàng)建以及編寫測試類的步驟,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-03-03

