設計模式之單例模式(Java)

設計模式之單例模式實現

單例模式是確保某個類只有一個實例,而且自行實例化並向整個系統提供這個實例。在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具有資源管理器的功能。每臺計算機可以有若干通信端口,系統應當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調用。總之,選擇單例模式就是爲了避免不一致狀態,避免多地同時修改狀態。
單例模式有以下特點:
1、單例類只能有一個實例。
2、單例類必須自己創建自己的唯一實例。
3、單例類必須給所有其他對象提供這一實例。

  Java 編寫單例模式有七種寫法
第一種 懶漢式單例

package com.xiaokai.constructor.singleton;

/**
 * Created by Administrator on 2016/8/4.
 */
public class Singleton1 {
    //私有靜態實例(全局共享)
    private static Singleton1 instance;
    //私有構造器(外部不能實例化)
    private Singleton1() {
    }
    //獲取單例
    public static Singleton1 getInstance() {
        //如果沒有實例化,先實例化返回
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }
}
單元測試類
package com.xiaokai.constructor.singleton;

import org.junit.Assert;
import org.junit.Test;


/**
 * Created by Administrator on 2016/8/4.
 */
public class Singleton1Test {
    @Test
    public void getInstance() throws Exception {
        Singleton1 singleton1 = Singleton1.getInstance();
        Singleton1 singleton11 = Singleton1.getInstance();
        Assert.assertSame(singleton1, singleton1);
    }

}
說明:此懶漢式單例在多線程不能正常工作(非線程安全),併發環境下很可能出現多個Singleton實例,要實現線程安全,有以下三種方式,都是對getInstance這個方法改造,保證了懶漢式單例的線程安全。

1、在getInstance方法上加同步

//在獲取單例方法上加鎖
public static synchronized Singleton1 getInstance() {
         if (single == null) {  
             single = new Singleton1();
         }  
        return single;
}
這種寫法雖然能夠在多線程中很好的工作,但是,他的效率很低,因爲單例已經存在的情況下是不需要同步的。

2、雙重檢查鎖定

//加鎖前先判單例是否已經存在,只有在不存的時候加鎖實例化出來一個單例
public static Singleton1 getInstance() {
        if (singleton == null) {
        //注意是類級鎖  
            synchronized (Singleton1.class) {  
               if (singleton == null) {  
                  singleton = new Singleton1(); 
               }  
            }  
        }  
        return singleton; 
    }
注意:雙重檢查鎖定在JDK1.5之後,才能夠正常達到單例效果。

3、使用靜態內部類實現單例模式(推薦)

public class Singleton { 
    //定義私有靜態內部類 
    private static class SingletonHolder { 
        //加載時實例化單例,static final類型
       private static final Singleton INSTANCE = new Singleton();  
    }
    //私有構造器  
    private Singleton (){}
    //通過調用getInstance實例化靜態內部類,返回靜態內部類加載時實例化的單例  
    public static final Singleton getInstance() {  
       return SingletonHolder.INSTANCE;  
    }  
}  
內部類方式既實現了線程安全,又避免了同步帶來的性能影響。這種方式利用了classloder的機制來保證初始化instance時只有一個線程,這種方式情況下,Singleton類被裝載了,instance不一定被初始化。因爲SingletonHolder類沒有被主動使用,只有顯式通過調用getInstance方法時,纔會顯式裝載SingletonHolder類,從而實例化instance。如果實例化instance很消耗資源,讓他延遲加載是很有幫助的。

第二種 餓漢式單例

package com.xiaokai.constructor.singleton;

/**
 * Created by Administrator on 2016/8/4.
 */

public class Singleton2 {
    private Singleton2() {
    }
    //在類初始化時,實例化單例
    private static final Singleton2 instance = new Singleton2();

    //靜態工廠方法
    public static Singleton2 getInstance() {
        return instance;
    }
}
單元測試類
package com.xiaokai.constructor.singleton;

import org.junit.Assert;
import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Created by Administrator on 2016/8/4.
 */
public class Singleton2Test {
    @Test
    public void getInstance() throws Exception {
        Singleton2 singleton1 = Singleton2.getInstance();
        Singleton2 singleton11 = Singleton2.getInstance();
        Assert.assertSame(singleton1, singleton1);
    }

}
餓漢式在類創建的同時就已經創建好一個靜態的對象供系統使用,以後不再改變,所以天生是線程安全的。不過,instance在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。

第三種 枚舉式單例(強烈推薦)

package com.xiaokai.constructor.singleton;

/**
 * Created by Administrator on 2016/8/4.
 */
public enum Singleton3 {
    INSTANCE;

    public void whateverMethod() {
    }
}
這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。

有兩個問題需要注意:
 1、如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的實例。假定不是遠端存取,例如一些servlet容器對每個servlet使用完全不同的類  裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的實例。
 2、如果Singleton實現了java.io.Serializable接口,那麼這個類的實例就可能被序列化和復原。不管怎樣,如果你序列化一個單例類的對象,接下來複原多個那個對象,那你就會有多個單例類的實例。

解決第一個問題的方法是重寫Object的getClass方法。
    private static Class getClass(String classname)
            throws ClassNotFoundException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        //先判空,再加載單例
        if (classLoader == null)
            classLoader = Singleton3.class.getClassLoader();

        return (classLoader.loadClass(classname));
    }
解決第二個問題的方法是實現序列化接口後重寫readResolve方法。
public class Singleton implements java.io.Serializable {
    public static Singleton INSTANCE = new Singleton();

    protected Singleton() {

    }

    private Object readResolve() {
        return INSTANCE;
    }
}

總結

三類:懶漢(懶漢又有雙重校驗鎖,靜態內部類),餓漢,枚舉。
懶漢:需要加鎖才能實現多線程同步,但是效率會降低。優點是延時加載。
雙重校驗鎖:麻煩,在當前Java內存模型中不一定都管用,某些平臺和編譯器甚至是錯誤的,在JDK1.5之後,才能夠正常達到單例效果。。
靜態內部類:延遲加載,減少內存開銷。因爲用到的時候才加載,避免了靜態field在單例類加載時即進入到堆內存的permanent代而永遠得不到回收的缺點(大多數垃圾回收算法是這樣)。
餓漢:因爲加載類的時候就創建實例,所以線程安全(多個ClassLoader存在時例外)。缺點是不能延時加載。
枚舉:很好,不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象。但是失去了類的一些特性,沒有延遲加載。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章