Java設計模式の單例模式


--------------------------------------------------

目錄

  1.定義

  2.常見的集中單例實現

    a.餓漢式,線程安全 但效率比較低

    b.單例模式的實現:飽漢式,非線程安全

    c.飽漢式,線程安全簡單實現

    d.線程安全 並且效率高  單例模式最優方案

    e:靜態內部類方式

  3.總結

    a.使用枚舉的單例模式

    b.使用枚舉,static處調用,初始化一次

  最終總結

--------------------------------------------------

 

1.定義

確保一個類只有一個實例,並提供一個全局訪問點!

2.常見的集中單例實現

  a.餓漢式,線程安全 但效率比較低

/** 
 * 單例模式的實現:餓漢式,線程安全 但效率比較低 
 */  
public class SingletonTest {  

    // 定義一個私有的構造方法
    private SingletonTest() {  
    }  

    // 將自身的實例對象設置為一個屬性,並加上Static和final修飾符
    private static final SingletonTest instance = new SingletonTest();  

    // 靜態方法返回該類的實例
    public static SingletonTest getInstancei() {  
        return instance;  
    }  
  
}

  b.單例模式的實現:飽漢式,非線程安全   

/**  
 * 單例模式的實現:飽漢式,非線程安全   
 *   
 */  
public class SingletonTest {

    // 定義私有構造方法(防止通過 new SingletonTest()去實例化)
    private SingletonTest() {   
    }   

    // 定義一個SingletonTest類型的變量(不初始化,注意這里沒有使用final關鍵字)
    private static SingletonTest instance;   

    // 定義一個靜態的方法(調用時再初始化SingletonTest,但是多線程訪問時,可能造成重復初始化問題)
    public static SingletonTest getInstance() {   
        if (instance == null)   
            instance = new SingletonTest();   
        return instance;   
    }   
} 

  c.飽漢式,線程安全簡單實現  

/**  
 * 單例模式的實現:飽漢式,線程安全簡單實現   
 *   
 */  
public class SingletonTest {

    // 定義私有構造方法(防止通過 new SingletonTest()去實例化)
    private SingletonTest() {   
    }   

    // 定義一個SingletonTest類型的變量(不初始化,注意這里沒有使用final關鍵字)
    private static SingletonTest instance;   

    // 定義一個靜態的方法(調用時再初始化SingletonTest,使用synchronized 避免多線程訪問時,可能造成重的復初始化問題)
    public static synchronized  SingletonTest getInstance() {   
        if (instance == null)   
            instance = new SingletonTest();   
        return instance;   
    }   
} 

  d.線程安全 並且效率高  單例模式最優方案

/**  
 * 單例模式最優方案
 * 線程安全  並且效率高  
 *  
 */  
public class SingletonTest { 

    // 定義一個私有構造方法
    private SingletonTest() { 
     
    }   
    //定義一個靜態私有變量(不初始化,不使用final關鍵字,使用volatile保證了多線程訪問時instance變量的可見性,避免了instance初始化時其他變量屬性還沒賦值完時,被另外線程調用)
    private static volatile SingletonTest instance;  

    //定義一個共有的靜態方法,返回該類型實例
    public static SingletonTest getIstance() { 
        // 對象實例化時與否判斷(不使用同步代碼塊,instance不等於null時,直接返回對象,提高運行效率)
        if (instance == null) {
            //同步代碼塊(對象未初始化時,使用同步代碼塊,保證多線程訪問時對象在第一次創建后,不再重復被創建)
            synchronized (SingletonTest.class) {
                //未初始化,則初始instance變量
                if (instance == null) {
                    instance = new SingletonTest();   
                }   
            }   
        }   
        return instance;   
    }   
}

 e:靜態內部類方式

/**
   * 靜態內部類方式
   *
   */
 public class Singleton {  
     private static class SingletonHolder {  
     private static final Singleton INSTANCE = new Singleton();  
     }  
     private Singleton (){}
     public static final Singleton getInstance() {  
         return SingletonHolder.INSTANCE;  
     }  
 }  
10 

  這種方式同樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟第三種和第四種方式不同的是(很細微的差別):第三種和第四種方式是只要Singleton類被裝載了,那么instance就會被實例化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,才會顯示裝載SingletonHolder類,從而實例化instance。想象一下,如果實例化instance很消耗資源,我想讓他延遲加載,另外一方面,我不希望在Singleton類加載時就實例化,因為我不能確保Singleton類還可能在其他的地方被主動使用從而被加載,那么這個時候實例化instance顯然是不合適的。這個時候,這種方式相比第三和第四種方式就顯得很合理。

3.總結

  

  【以上單例模式】傳統的兩私有一公開(私有構造方法、私有靜態實例(懶實例化/直接實例化)、公開的靜態獲取方法)涉及線程安全問題(即使有多重檢查鎖也可以通過反射破壞單例)

目前最為安全的實現單例的方法是通過內部靜態enum的方法來實現,因為JVM會保證enum不能被反射並且構造器方法只執行一次。如下

  a.使用枚舉的單例模式

 

/**
 * 使用枚舉的單例模式
 *
 * @author yzl
 * @see [相關類/方法](可選)
 * @since [產品/模塊版本] (可選)
 */
public class EnumSingleton{
    private EnumSingleton(){}
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }
    
    private static enum Singleton{
        INSTANCE;
        
        private EnumSingleton singleton;
        //JVM會保證此方法絕對只調用一次
        private Singleton(){
            singleton = new EnumSingleton();
        }
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}

  b.使用枚舉,static處調用,初始化一次

import java.util.ArrayList;
import java.util.List;

/**
 * 初始化的優雅實現
 * 可以在static處調用,
 * 也可以在普通方法里調用,都保證只初始化一次
 * 
 * 當然將enum塊的代碼直接放到StaticInitTest類的private static 方法里做也是可以的
 *
 * @author yzl
 * @see [相關類/方法](可選)
 * @since [產品/模塊版本] (可選)
 */
public class StaticInitTest {
    private static List<Integer> dataList = null;
    
    static{
        dataList = Singleton.INSTANCE.init();
    }
    
    /**
     * 
     * 單例模式來填充數據
     *
     * @author yzl
     * @see [相關類/方法](可選)
     * @since [產品/模塊版本] (可選)
     */
    private static enum Singleton {
        INSTANCE;
        private List<Integer> list;
        
        private Singleton(){
            fillData();
        }
        /**
         * 
         * 初始化數據
         *
         * @see [相關類/方法](可選)
         * @since [產品/模塊版本](可選)
         */
        private void fillData(){
            list = new ArrayList<Integer>(5);
            for(int i =1; i<6; i++){
                list.add(i);
            }
        }

        /**
         * 
         * 初始化的入口
         *
         * @see [相關類/方法](可選)
         * @since [產品/模塊版本](可選)
         */
        public List<Integer> init(){
            return list;
        }
    }
}

最終總結

有兩個問題需要注意:

     1、如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的實例。假定不是遠端存取,例如一些servlet容器對每個servlet使用完全不同的類  裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的實例。

     2、如果Singleton實現了java.io.Serializable接口,那么這個類的實例就可能被序列化和復原。不管怎樣,如果你序列化一個單例類的對象,接下來復原多個那個對象,那你就會有多個單例類的實例。

對第一個問題修復的辦法是:

private static Class getClass(String classname) throws ClassNotFoundException {     
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
        
        if(classLoader == null)     
          classLoader = Singleton.class.getClassLoader();     
        
      return (classLoader.loadClass(classname));     
    }     
 }  

 對第二個問題修復的辦法是:

  public class Singleton implements java.io.Serializable {     
     public static Singleton INSTANCE = new Singleton();     
        
     protected Singleton() {     
          
     }     
     private Object readResolve() {     
              return INSTANCE;     
       }    
 }   

  對我來說,我比較喜歡第a和e種方式,簡單易懂,而且在JVM層實現了線程安全(如果不是多個類加載器環境),一般的情況下,我會使用第a種方式,只有在要明確實現lazy loading效果時才會使用第e種方式,另外,如果涉及到反序列化創建對象時我會試着使用枚舉的方式來實現單例,不過,我一直會保證我的程序是線程安全的,如果有其他特殊的需求,我可能會使用第七種方式,畢竟,JDK1.5已經沒有雙重檢查鎖定的問題了。

 

參考資料:java單例之enum實現方式

       http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html

     java設計模式--單例模式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM