1. 程式人生 > 實用技巧 >單例模式的幾種實現方式

單例模式的幾種實現方式

1.餓漢式單例

    package org.mlinge.s01;  
      
    public class MySingleton {  
          
        private static MySingleton instance = new MySingleton();  
          
        private MySingleton(){}  
          
        public static MySingleton getInstance() {  
            return instance;  
        }  
          
    }  

2.懶漢式單例

 package org.mlinge.s02;  
      
    public class MySingleton {  
          
        private static MySingleton instance = null;  
          
        private MySingleton(){}  
          
        public static MySingleton getInstance() {  
            if(instance == null){  
                instance 
= new MySingleton(); } return instance; } }

3.執行緒安全的懶漢式單例

(1)、 方法中宣告synchronized關鍵字

public class MySingleton {  
          
        private static MySingleton instance = null;  
          
        private MySingleton(){}  
          
        public synchronized
static MySingleton getInstance() { try { if(instance != null){//懶漢式 }else{ //建立例項之前可能會有一些準備性的耗時工作 Thread.sleep(300); instance = new MySingleton(); } } catch (InterruptedException e) { e.printStackTrace(); } return instance; } }

(2)、 同步程式碼塊實現

package org.mlinge.s03;  
      
    public class MySingleton {  
          
        private static MySingleton instance = null;  
          
        private MySingleton(){}  
          
        //public synchronized static MySingleton getInstance() {  
        public static MySingleton getInstance() {  
            try {   
                synchronized (MySingleton.class) {  
                    if(instance != null){//懶漢式   
                          
                    }else{  
                        //建立例項之前可能會有一些準備性的耗時工作   
                        Thread.sleep(300);  
                        instance = new MySingleton();  
                    }  
                }  
            } catch (InterruptedException e) {   
                e.printStackTrace();  
            }  
            return instance;  
        }  
    }  

4.正確的雙重檢查鎖


public class Singleton {
    private volatile static Singleton uniqueSingleton;

    private Singleton() {
    }

    public Singleton getInstance() {
        if (null == uniqueSingleton) {
            synchronized (Singleton.class) {
                if (null == uniqueSingleton) {
                    uniqueSingleton = new Singleton();
                }
            }
        }
        return uniqueSingleton;
    }
}

// 需要在`uniqueSingleton`前加入關鍵字`volatile`。使用了volatile關鍵字後,重排序被禁止,所有的寫(write)操作都將發生在讀(read)操作之前

5.靜態內部類模式(高併發下單例)

public class SingleTon{
  private SingleTon(){}
 
  private static class SingleTonHoler{
     private static SingleTon INSTANCE = new SingleTon();
 }
 
  public static SingleTon getInstance(){
    return SingleTonHoler.INSTANCE;
  }
}

  靜態內部類的優點是:外部類載入時並不需要立即載入內部類,內部類不被載入則不去初始化INSTANCE,故而不佔記憶體。即當SingleTon第一次被載入時,並不需要去載入  SingleTonHoler,只有當getInstance()方法第一次被呼叫時,才會去初始化INSTANCE,第一次呼叫getInstance()方法會導致虛擬機器載入SingleTonHoler類,這種方法不僅能確保執行緒安全,也能保證單例的唯一性,同時也延遲了單例的例項化。

類載入時機:JAVA虛擬機器在有且僅有的5種場景下會對類進行初始化。

  1.遇到new、getstatic、setstatic或者invokestatic這4個位元組碼指令時,對應的java程式碼場景為:new一個關鍵字或者一個例項化物件時、讀取或設定一個靜態欄位時(final修飾、已在編譯期把結果放入常量池的除外)、呼叫一個類的靜態方法時。
  2.使用java.lang.reflect包的方法對類進行反射呼叫的時候,如果類沒進行初始化,需要先呼叫其初始化方法進行初始化。
  3.當初始化一個類時,如果其父類還未進行初始化,會先觸發其父類的初始化。
  4.當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main()方法的類),虛擬機器會先初始化這個類。
  5.當使用JDK 1.7等動態語言支援時,如果一個java.lang.invoke.MethodHandle例項最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行過初始化,則需要先觸發其初始化。
這5種情況被稱為是類的主動引用,注意,這裡《虛擬機器規範》中使用的限定詞是"有且僅有",那麼,除此之外的所有引用類都不會對類進行初始化,稱為被動引用。靜態內部類就屬於被動引用的行列。


  我們再回頭看下getInstance()方法,呼叫的是SingleTonHoler.INSTANCE,取的是SingleTonHoler裡的INSTANCE物件,跟上面那個DCL方法不同的是,getInstance()方法並沒有多次去new物件,故不管多少個執行緒去呼叫getInstance()方法,取的都是同一個INSTANCE物件,而不用去重新建立。當getInstance()方法被呼叫時,SingleTonHoler才在SingleTon的執行時常量池裡,把符號引用替換為直接引用,這時靜態物件INSTANCE也真正被建立,然後再被getInstance()方法返回出去,這點同餓漢模式。那麼INSTANCE在建立過程中又是如何保證執行緒安全的呢?在《深入理解JAVA虛擬機器》中,有這麼一句話:

  虛擬機器會保證一個類的<clinit>()方法在多執行緒環境中被正確地加鎖、同步,如果多個執行緒同時去初始化一個類,那麼只會有一個執行緒去執行這個類的<clinit>()方法,其他執行緒都需要阻塞等待,直到活動執行緒執行<clinit>()方法完畢。如果在一個類的<clinit>()方法中有耗時很長的操作,就可能造成多個程序阻塞(需要注意的是,其他執行緒雖然會被阻塞,但如果執行<clinit>()方法後,其他執行緒喚醒之後不會再次進入<clinit>()方法。同一個載入器下,一個型別只會初始化一次。),在實際應用中,這種阻塞往往是很隱蔽的。

  故而,可以看出INSTANCE在建立過程中是執行緒安全的,所以說靜態內部類形式的單例可保證執行緒安全,也能保證單例的唯一性,同時也延遲了單例的例項化。

注: 內容源自網際網路整理,侵權請聯絡刪除