欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java單例模式的8種寫法(推薦)

 更新時(shí)間:2021年01月19日 11:10:28   作者:Zerone Wong  
這篇文章主要介紹了Java單例模式的8種寫法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

單例:Singleton,是指僅僅被實(shí)例化一次的類。

餓漢單例設(shè)計(jì)模式

一、餓漢設(shè)計(jì)模式

public class SingletonHungry {
 private final static SingletonHungry INSTANCE = new SingletonHungry();

 private SingletonHungry() {
 }

 public static SingletonHungry getInstance() {
 return INSTANCE;
 }
}

因?yàn)閱卫龑ο笠婚_始就初始化了,不會(huì)出現(xiàn)線程安全的問題。

PS:因?yàn)槲覀冎恍枰跏蓟?次,所以給INSTANCE加了final關(guān)鍵字,表明初始化1次后不再允許初始化。

懶漢單例設(shè)計(jì)模式

二、簡單懶漢設(shè)計(jì)模式

由于餓漢模式一開始就初始化好了,但如果一直沒有被使用到的話,是會(huì)浪費(fèi)珍貴的內(nèi)存資源的,所以引出了懶漢模式。

懶漢:首次使用時(shí)才會(huì)去實(shí)例化對象。

public class SingletonLazy1 {
 private static SingletonLazy1 instance;

 private SingletonLazy1() {
 }

 public static SingletonLazy1 getInstance() {
 if (instance == null) {
  instance = new SingletonLazy1();
 }
 return instance;
 }
}

測試:

public class Main {
 public static void main(String[] args) {
 SingletonLazy1 instance1 = SingletonLazy1.getInstance();
 SingletonLazy1 instance2 = SingletonLazy1.getInstance();
 System.out.println(instance1);
 System.out.println(instance2);
 }
}

測試結(jié)果:從結(jié)果可以看出,打印出來的兩個(gè)實(shí)例對象地址是一樣的,所以認(rèn)為是只創(chuàng)建了一個(gè)對象。

在這里插入圖片描述

三、進(jìn)階

1:解決多線程并發(fā)問題

上述代碼存在的問題:在多線程環(huán)境下,不能保證只創(chuàng)建一個(gè)實(shí)例,我們進(jìn)行問題的重現(xiàn):

public class Main {
 public static void main(String[] args) {
 new Thread(()-> System.out.println(SingletonLazy1.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy1.getInstance())).start();
 }
}

結(jié)果:獲取到的對象不一樣,這并不是我們的預(yù)期結(jié)果。

在這里插入圖片描述

解決方案:

public class SingletonLazy2 {
 private static SingletonLazy2 instance;

 private SingletonLazy2() {
 }
 //在方法加synchronized修飾符
 public static synchronized SingletonLazy2 getInstance() {
 if (instance == null) {
  instance = new SingletonLazy2();
 }
 return instance;
 }
}

測試:

public class Main2 {
 public static void main(String[] args) {
 new Thread(()-> System.out.println(SingletonLazy2.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy2.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy2.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy2.getInstance())).start();
 }
}

在這里插入圖片描述

結(jié)果:多線程環(huán)境下獲取到的是同個(gè)對象。

四、進(jìn)階2:縮小方法鎖粒度

上一方案雖然解決了多線程問題,但由于synchronized關(guān)鍵字是加在方法上的,鎖粒度很大,當(dāng)有上萬甚至更多的線程同時(shí)訪問時(shí),都被攔在了方法外,大大降低了程序性能,所以我們要適當(dāng)縮小鎖粒度,控制鎖的范圍在代碼塊上。

public class SingletonLazy3 {
 private static SingletonLazy3 instance;

 private SingletonLazy3() {
 }
 
 public static SingletonLazy3 getInstance() {
 //代碼塊1:不要在if外加鎖,不然和鎖方法沒什么區(qū)別
 if (instance == null) {
  //代碼塊2:加鎖,將方法鎖改為鎖代碼塊
  synchronized (SingletonLazy3.class) {
  //代碼塊3
  instance = new SingletonLazy3();
  }
 }
 return instance;
 }
}

測試:

public class Main3 {
 public static void main(String[] args) {
 new Thread(()-> System.out.println(SingletonLazy3.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy3.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy3.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy3.getInstance())).start();
 }
}

我們看一下運(yùn)行結(jié)果:還是出現(xiàn)了線程安全的問題(每次執(zhí)行都可能打印不同的地址情況,只要證明是非線程安全的即可)。

在這里插入圖片描述

原因分析:當(dāng)線程A拿到鎖進(jìn)入到代碼塊3并且還沒有創(chuàng)建完實(shí)例時(shí),線程B是有機(jī)會(huì)到達(dá)代碼塊2的,此時(shí)線程C和D可能在代碼塊1,當(dāng)線程A執(zhí)行完之后釋放鎖并返回對象1,線程B進(jìn)入進(jìn)入代碼塊3,又創(chuàng)建了新的對象2覆蓋對象1并返回,最后當(dāng)線程C和D在進(jìn)行判null時(shí)發(fā)現(xiàn)instance非空,直接返回最后創(chuàng)建的對象2。

五、進(jìn)階3:雙重檢查鎖DCL(Double-Checked-Locking)

所謂雙重檢查鎖,就是在線程獲取到鎖之后再對實(shí)例進(jìn)行第2次判空檢查,判斷是不是有上一個(gè)線程已經(jīng)進(jìn)行了實(shí)例化,有的話直接返回即可,否則進(jìn)行實(shí)例初始化。

public class SingletonLazy4DCL {
 private static SingletonLazy4DCL instance;

 private SingletonLazy4DCL() {
 }

 public static SingletonLazy4DCL getInstance() {
 //代碼塊1:第一次判空檢查
 if (instance == null) {
  //代碼塊2:加鎖,將方法鎖改為鎖代碼塊
  synchronized (SingletonLazy3.class) {
  //代碼塊3:進(jìn)行第二次(雙重)判空檢查
  if (instance == null) {
   instance = new SingletonLazy4DCL();
  }
  }
 }
 return instance;
 }
}

測試:

public class Main4DCL {
 public static void main(String[] args) {
 new Thread(()-> System.out.println(SingletonLazy4DCL.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy4DCL.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy4DCL.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy4DCL.getInstance())).start();
 }
}

在這里插入圖片描述

六、進(jìn)階4:禁止指令重排

在對象的實(shí)例過程中,大概可分為以下3個(gè)步驟:

  1. 分配對象內(nèi)存空間
  2. 在空間中創(chuàng)建對象
  3. 實(shí)例指向分配到的內(nèi)存空間地址

由于實(shí)例化對象的過程不是原子性的,且JVM本身對Java代碼指令有重排的操作,可能1-2-3的操作被重新排序成了1-3-2,這樣就會(huì)導(dǎo)致在3執(zhí)行完之后還沒來得及創(chuàng)建對象時(shí),其他線程先讀取到了未初始化的對象instance并提前返回,在使用的時(shí)候會(huì)出現(xiàn)NPE空指針異常。

解決:給instance加volatile關(guān)鍵字表明禁止指令重排,出現(xiàn)的概率不大, 但這是更安全的一種做法。

public class SingletonLazy5Volatile {
 //加volatile關(guān)鍵字
 private volatile static SingletonLazy5Volatile instance;

 private SingletonLazy5Volatile() {
 }

 public static SingletonLazy5Volatile getInstance() {
 //代碼塊1
 if (instance == null) {
  //代碼塊2:加鎖,將方法鎖改為鎖代碼塊
  synchronized (SingletonLazy3.class) {
  //代碼塊3
  if (instance == null) {
   instance = new SingletonLazy5Volatile();
  }
  }
 }
 return instance;
 }
}

七、進(jìn)階5:靜態(tài)內(nèi)部類

我們還可以使用靜態(tài)類的靜態(tài)變量被第一次訪問時(shí)才會(huì)進(jìn)行初始化的特性來進(jìn)行懶加載初始化。把外部類的單例對象放到靜態(tài)內(nèi)部類的靜態(tài)成員變量里進(jìn)行初始化。

public class SingletonLazy6InnerStaticClass {
 private SingletonLazy6InnerStaticClass() {
 }

 public static SingletonLazy6InnerStaticClass getInstance() {
 return SingletonLazy6InnerStaticClass.InnerStaticClass.instance;
 //或者寫成return InnerStaticClass.instance;
 }

 private static class InnerStaticClass {
 private static final SingletonLazy6InnerStaticClass instance = new SingletonLazy6InnerStaticClass();
 }
}

雖然靜態(tài)內(nèi)部類里的寫法和餓漢模式很像,但它卻不是在外部類加載時(shí)就初始化了,而是在第一次被訪問到時(shí)才會(huì)進(jìn)行初始化的操作(即getInstance方法被調(diào)用時(shí)),也就起到了懶加載的效果,并且它可以保證線程安全。

測試:

public class Main6InnerStatic {
 public static void main(String[] args) {
 new Thread(()-> System.out.println(SingletonLazy6InnerStaticClass.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy6InnerStaticClass.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy6InnerStaticClass.getInstance())).start();
 new Thread(()-> System.out.println(SingletonLazy6InnerStaticClass.getInstance())).start();
 }
}

在這里插入圖片描述

反射攻擊

雖然我們一開始都對構(gòu)造器進(jìn)行了私有化處理,但Java本身的反射機(jī)制卻還是可以將private訪問權(quán)限改為可訪問,依舊可以創(chuàng)建出新的實(shí)例對象,這里以餓漢模式舉例說明:

public class MainReflectAttack {
 public static void main(String[] args) {
 try {
  SingletonHungry normal1 = SingletonHungry.getInstance();
  SingletonHungry normal2 = SingletonHungry.getInstance();
  //開始反射創(chuàng)建實(shí)例
  Constructor<SingletonHungry> reflect = SingletonHungry.class.getDeclaredConstructor(null);
  reflect.setAccessible(true);
  SingletonHungry attack = reflect.newInstance();
  
  System.out.println("正常靜態(tài)方法調(diào)用獲取到的對象:");
  System.out.println(normal1);
  System.out.println(normal2);
  System.out.println("反射獲取到的對象:");
  System.out.println(attack);
 } catch (Exception e) {
  e.printStackTrace();
 }
 }
}

在這里插入圖片描述

八、枚舉單例(推薦使用)

public enum SingletonEnum {
 INSTANCE;
}

枚舉是最簡潔、線程安全、不會(huì)被反射創(chuàng)建實(shí)例的單例實(shí)現(xiàn),《Effective Java》中也表明了這種寫法是最佳的單例實(shí)現(xiàn)模式。

單元素的枚舉類型經(jīng)常成為實(shí)現(xiàn)Singleton的最佳方法。 --《Effective Java》

為什么說不會(huì)被反射創(chuàng)建對象呢?查閱構(gòu)造器反射實(shí)例化對象方法newInstance的源碼可知:反射禁止了枚舉對象的實(shí)例化,也就防止了反射攻擊,不用自己在構(gòu)造器實(shí)現(xiàn)復(fù)雜的重復(fù)實(shí)例化邏輯了。

在這里插入圖片描述

測試:

public class MainEnum {
 public static void main(String[] args) {
 SingletonEnum instance1 = SingletonEnum.INSTANCE;
 SingletonEnum instance2 = SingletonEnum.INSTANCE;
 System.out.println(instance1.hashCode());
 System.out.println(instance2.hashCode());
 }
}

在這里插入圖片描述

總結(jié):幾種實(shí)現(xiàn)方式的優(yōu)缺點(diǎn) 懶漢模式

優(yōu)點(diǎn):節(jié)省內(nèi)存。

缺點(diǎn):存在線程安全問題,若要保證線程安全,則寫法復(fù)雜。

餓漢模式

優(yōu)點(diǎn):線程安全。

缺點(diǎn):如果單例對象一直沒被使用,則會(huì)浪費(fèi)內(nèi)存空間。

靜態(tài)內(nèi)部類

優(yōu)點(diǎn):懶加載并避免了多線程問題,寫法相比于懶漢模式更簡單。

缺點(diǎn):需要多創(chuàng)建一個(gè)內(nèi)部類。

枚舉

優(yōu)點(diǎn):簡潔、天生線程安全、不可反射創(chuàng)建實(shí)例。

缺點(diǎn):暫無

到此這篇關(guān)于Java單例模式的8種寫法的文章就介紹到這了,更多相關(guān)Java單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解SpringBoot配置文件啟動(dòng)時(shí)動(dòng)態(tài)配置參數(shù)方法

    詳解SpringBoot配置文件啟動(dòng)時(shí)動(dòng)態(tài)配置參數(shù)方法

    這篇文章主要介紹了詳解SpringBoot配置文件啟動(dòng)時(shí)動(dòng)態(tài)配置參數(shù)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • springboot 集成pgsql+mybatis plus的詳細(xì)步驟

    springboot 集成pgsql+mybatis plus的詳細(xì)步驟

    集成 Spring Boot、PostgreSQL 和 MyBatis Plus 的步驟與 MyBatis 類似,只不過在 MyBatis Plus 中提供了更多的便利功能,如自動(dòng)生成 SQL、分頁查詢、Wrapper 查詢等,下面分步驟給大家介紹springboot 集成pgsql+mybatis plus的過程,感興趣的朋友一起看看吧
    2023-12-12
  • Java分支結(jié)構(gòu)程序設(shè)計(jì)實(shí)例詳解

    Java分支結(jié)構(gòu)程序設(shè)計(jì)實(shí)例詳解

    這篇文章主要介紹了Java分支結(jié)構(gòu)程序設(shè)計(jì)例題,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-01-01
  • Java追加文件內(nèi)容的三種方法實(shí)例代碼

    Java追加文件內(nèi)容的三種方法實(shí)例代碼

    本篇文章主要介紹了Java追加文件內(nèi)容的三種方法實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-04-04
  • 在Java的Struts框架中ONGL表達(dá)式的基礎(chǔ)使用入門

    在Java的Struts框架中ONGL表達(dá)式的基礎(chǔ)使用入門

    這篇文章主要介紹了深入解析在Java的Struts框架中ONGL表達(dá)式的基礎(chǔ)使用入門,Struts框架是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下
    2015-11-11
  • 教你怎么用Java開發(fā)掃雷游戲

    教你怎么用Java開發(fā)掃雷游戲

    我們那時(shí)候上機(jī)經(jīng)常玩掃雷,試想如果我當(dāng)年可以用 java 寫個(gè)掃雷出來,那場面不用我多說了吧,大家讓開,我要開始裝逼了,之前用JavaScript寫過了一個(gè)掃雷,這次我用java再寫了一遍,權(quán)當(dāng)是復(fù)習(xí)咯.文中有非常詳細(xì)的代碼示例,需要的朋友可以參考下
    2021-05-05
  • springboot注冊攔截器所遇到的問題

    springboot注冊攔截器所遇到的問題

    這篇文章主要介紹了springboot注冊攔截器的方法及所遇到的問題,需要的朋友可以參考下
    2018-07-07
  • 詳解Mybatis 傳遞參數(shù)類型為List的取值問題

    詳解Mybatis 傳遞參數(shù)類型為List的取值問題

    這篇文章主要介紹了詳解Mybatis 傳遞參數(shù)類型為List的取值問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • springboot自定義starter方法及注解實(shí)例

    springboot自定義starter方法及注解實(shí)例

    這篇文章主要為大家介紹了springboot自定義starter方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • SpringCloud微服務(wù)集成Dubbo的詳細(xì)過程

    SpringCloud微服務(wù)集成Dubbo的詳細(xì)過程

    Apache?Dubbo?是一款易用、高性能的?WEB?和?RPC?框架,同時(shí)為構(gòu)建企業(yè)級微服務(wù)提供服務(wù)發(fā)現(xiàn)、流量治理、可觀測、認(rèn)證鑒權(quán)等能力、工具與最佳實(shí)踐,這篇文章主要介紹了SpringCloud微服務(wù)集成Dubbo,需要的朋友可以參考下
    2024-03-03

最新評論