Java單例模式的幾種實現

 

轉載請註明原文地址:http://www.javashuo.com/article/p-xfppzqug-dv.htmlhtml

 

一:靜態內部類實現單例模式

  原理:經過一個靜態內部類定義一個靜態變量來持有當前類實例,在類加載時就建立好,在使用時獲取。java

  缺點:沒法作到延遲建立對象,在類加載時進行建立會致使初始化時間變長。多線程

public class SingletonInner {
    private static class Holder {
        private static SingletonInner singleton = new SingletonInner();
    }
 
    private SingletonInner(){}
 
    public static SingletonInner getSingleton(){
        return Holder.singleton;
    }

 

二:餓漢模式

  原理:建立好一個靜態變量,每次要用時直接返回。併發

  缺點:沒法作到延遲建立對象,在類加載時進行建立會致使初始化時間變長。函數

public class SingletonHungry {
 
    private static SingletonHungry instance = new SingletonHungry();
 
    private SingletonHungry() {
    }
 
    public static SingletonHungry getInstance() {
        return instance;
    }

 

 

 

三:懶漢模式

  原理:延遲建立,在第一次用時才建立,以後每次用到就返回建立好的。性能

  缺點:因爲synchronized的存在,多線程時效率很低。this

public class SingletonLazy {
 
    private static volatile SingletonLazy instance;
 
    private SingletonLazy() {
    }
 
    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

 

 

四:雙重校驗鎖懶漢模式

  原理:在getSingleton()方法中,進行兩次null檢查。這樣能夠極大提高併發度,進而提高性能。spa

public class Singleton {
    private static volatile Singleton singleton = null;//使用valatile,使該變量能被全部線程可見
 
    private Singleton(){}
 
    public static Singleton getSingleton(){
        if(singleton == null){
            synchronized (Singleton.class){//初始化時,加鎖建立
                if(singleton == null){//爲避免在加鎖過程當中被其餘線程建立了,再做一次非空校驗
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }    

 

五:上述4種方式實現單例模式的缺點

  一、反序列化對象時會破環單例線程

  反序列化對象時不會調用getXX()方法,因而繞過了確保單例的邏輯,直接生成了一個新的對象,破環了單例。code

  解決辦法是:重寫類的反序列化方法,在反序列化方法中返回單例而不是建立一個新的對象。

public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     
 
   protected Singleton() {     
   }  
 
   //反序列時直接返回當前INSTANCE
   private Object readResolve() {     
            return INSTANCE;     
      }    
}

  二、在代碼中經過反射機制,直接調用類的私有構造函數建立新的對象,會破環單例

  解決辦法是:維護一個volatile的標誌變量在第一次建立實例時置爲false;重寫構造函數,根據標誌變量決定是否容許建立。

private static volatile  boolean  flag = true;
private Singleton(){
    if(flag){
    flag = false;   //第一次建立時,改變標誌
    }else{
        throw new RuntimeException("The instance  already exists !");
    }


六:枚舉模式實現單例——將單例維護在枚舉類中做爲惟一實例

  原理:定義枚舉類型,裏面只維護一個實例,以此保證單例。每次取用時都從枚舉中取,而不會取到其餘實例。

public enum  SingletonEnum {
    INSTANCE;
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }

  優勢:

  1)使用SingletonEnum.INSTANCE進行訪問,無需再定義getInstance方法和調用該方法。

  2)JVM對枚舉實例的惟一性,避免了上面提到的反序列化和反射機制破環單例的狀況出現:每個枚舉類型和定義的枚舉變量在JVM中都是惟一的。 

    緣由:枚舉類型在序列化時僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是經過java.lang.Enum的valueOf方法來根據名字查找枚舉對象

      同時,編譯器禁止重寫枚舉類型的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

相關文章
相關標籤/搜索