淺析Java單例設計模式(自寫demo)
單例模式特點
1、構造器私有
2、在一個Java應用程序中,可保證隻有一個實例對象
3、隻提供一個供外界調用的getInstance()方法
單例模式優點
1、減少某些對象的頻繁創建,降低系統開銷和內存占用
2、外部調用不使用new關鍵字,降低系統內存的使用頻率
3、對於特殊的類,在系統中隻能存在一個實例,否則系統無法正常運行,比如Controller
實現方式
這裡簡單介紹兩種實現方式
餓漢式(線程安全)
/** * @author: xuzhilei6656 * @create: 2021-12-12 12:07 * @description: 單例模式(餓漢式) **/ public class Singleton { //創建實例 private static Singleton instance = new Singleton(); //私有構造器 private Singleton(){ } //獲取實例的靜態方法 public static Singleton getInstance(){ return instance; } }
實例對象在類被加載的時候就已經完成初始化,外界調用拿到的都是這個唯一的實例對象
懶漢式
/** * @author: xuzhilei6656 * @create: 2021-12-12 12:22 * @description: 單例模式(懶漢式) **/ public class Singleton { //聲明一個變量 private static Singleton instance; //私有構造器 private Singleton(){ } //獲取實例的靜態方法 public static Singleton getInstance(){ //如果是首次調用,實例對象還沒有被創建,就需要創建,否則都是返回已經創建過的那個對象 if (instance == null){ instance = new Singleton(); } return instance; } }
對比餓漢式可見,實例對象在類被加載的時候並沒有進行創建,在首次調用的時候才被創建,以後再被調用,返回的也是那個唯一的實例對象。
在多線程情況下,這種寫法存在線程安全問題,比如:線程A在執行完if判斷條件後進入阻塞狀態,此時並沒有進行對象創建,此時線程B來瞭,在執行完if條件後直接進行對象創建,等線程A恢復運行狀態後也會進行對象創建,這個時候就不符合單例模式瞭,即出現瞭線程不安全的問題。
解決方案:在獲取實例的靜態方法上加synchronized關鍵字,即加鎖
/** * @author: xuzhilei6656 * @create: 2021-12-12 12:22 * @description: 單例模式(懶漢式) **/ public class Singleton { //聲明一個變量 private static Singleton instance; //私有構造器 private Singleton(){ } //獲取實例的靜態方法 public static synchronized Singleton getInstance(){ //如果是首次調用,實例對象還沒有被創建,就需要創建,否則都是返回已經創建過的那個對象 if (instance == null){ instance = new Singleton(); } return instance; } }
簡單粗暴,可達到我們的目的,但是每次獲取實例對象都要有加鎖操作,影響系統性能。
改進後的方案:雙重檢查
/** * @author: xuzhilei6656 * @create: 2021-12-12 12:22 * @description: 單例模式(懶漢式) **/ public class Singleton { //聲明一個變量 private static Singleton instance; //私有構造器 private Singleton(){ } //獲取實例的靜態方法 public static synchronized Singleton getInstance(){ //第一次檢查 if (instance == null){ //獲取鎖 synchronized (Singleton.class){ //第二次檢查 if (instance==null){ //兩次檢查都確定沒有已存在的實例對象,這才進行對象的創建操作 instance = new Singleton(); } } } return instance; } }
這樣不必每次獲取實例對象的時候都進行加鎖操作,隻有在第一次創建對象的時候才進行加鎖操作,提高瞭系統性能。
但是,即使這樣有可能會出現。因為 instance = new Singleton()這行代碼在JVM中是兩個操作,賦值和初始化實例,但JVM並不保證這兩個操作的順序,有可能JVM給新對象分配瞭空間,直接賦值給instance變量,然後才去做初始化實例操作。比如下面這種情況
1,A,B兩個線程都進入第一個if條件
2,A線程先搶到鎖進入到synchronized代碼塊,執行瞭instance = new Singleton()這行代碼,然後釋放鎖,此時有可能JVM隻給實例對象分配瞭空白的內存空間,並沒有執行初始化操作
3,B線程搶到鎖,進入到synchronized代碼塊,第二次判斷的時候發現instance不是null,直接返回使用卻發現得到的對象還沒有被初始化,於是出現瞭問題。
再次改進:使用volatile關鍵字修飾聲明的成員變量instance
/** * @author: xuzhilei6656 * @create: 2021-12-12 12:22 * @description: 單例模式(懶漢式) **/ public class Singleton { //聲明變量,被volatile修飾 private volatile static Singleton instance; //私有構造器 private Singleton(){ } //獲取實例的靜態方法 public static synchronized Singleton getInstance(){ //第一次檢查 if (instance == null){ //獲取鎖 synchronized (Singleton.class){ //第二次檢查 if (instance==null){ //兩次檢查都確定沒有已存在的實例對象,這才進行對象的創建操作 instance = new Singleton(); } } } return instance; } }
volatile關鍵字作用:通過volatile修飾的變量,不會被線程本地緩存,所有線程對該對象的讀寫都會第一時間同步到主內存,從而保證多個線程間該對象的準確性。
這個寫法已經比較完美瞭,既能保證安全的創建出唯一實例,又不會對系統性能有太大影響。
不過,還有更優的寫法:靜態內部類實現
/** * @author: xuzhilei6656 * @create: 2021-12-12 15:17 * @description: 單例模式 **/ public class Singleton { //私有構造器 private Singleton() { } //靜態內部類聲明實例 private static class SingletonFactory{ private static Singleton instance = new Singleton(); } //獲取實例的靜態方法 public static Singleton getInstance(){ return SingletonFactory.instance; } }
使用內部類來維護單例的實現,JVM內部的機制能夠保證當一個類被加載的時候,這個類的加載過程是線程互斥的。這樣當我們第一次調用getInstance()方法的時候,JVM能夠保證創建出唯一的實例對象,並且這個實例對象是已經被初始化完成的,就解決瞭上面的線程安全問題
最後一種實現單例的寫法也很完美,代碼最簡潔
通過枚舉
/** * @author: xuzhilei6656 * @create: 2021-12-12 15:33 * @description: 單例模式 **/ public enum Singleton { //代表一個Singleton實例 INSTANCE; }
通過枚舉來實現單實例代碼更加簡潔,而且JVM從根本上保證實例對象的唯一性,是更簡潔、高效、安全的實現單例的方式
以上就是淺析Java單例設計模式(自寫demo)的詳細內容,更多關於Java單例模式的資料請關註WalkonNet其它相關文章!