設計模式——單例模式
單例模式:如果程序運行過程中,某個類的實例只有唯一的一份,那麼可以說這個類就是單例的,相應的設計模式稱謂單例模式。這種模式涉及到一個單一的類,該類自己負責創建自己的實例,同時必須確保這運行過程中只能有一個實例被創建。
單例模式的實現方式有:餓漢式,懶漢式,雙重校驗式,靜態內部類方式,枚舉方式,下面具體每一種方式的實現代碼並分析優劣。
1-餓漢式
/**
* 單例模式:餓漢式,天然線程安全,在初始化時立即加載這個對象
*/
public class Singleton1 {
//0-私有化構造器
private Singleton1(){}
//1-類初始化時立即加載對象
private static Singleton1 singleton = new Singleton1();
//2-靜態方法提供單例對象
public static Singleton1 getSingleton(){
return singleton;
}
}
|
餓漢式:天然線程安全,沒有加鎖,效率較高,在類加載的時候就初始化實例,可能會產生垃圾對象,沒有達到懶加載的效果。
測試代碼:
@Test
public void testSingleton1(){
Singleton1 singleton1 = Singleton1.getSingleton();
Singleton1 singleton2 = Singleton1.getSingleton();
//斷言兩者是同一對象
Assert.assertTrue(singleton1 == singleton2);
}
|
2-懶漢式
/**
* 懶漢式,在需要使用對象時在new出來,有線程安全問題
* 資源利用率提高了,但是每次調用的時候都要同步,併發效率低了
*/
public class Singleton2 {
//0-私有化構造器
private Singleton2(){
//多次調用直接拋出異常,確保單例
if(null != singleton){
throw new RuntimeException("單例不允許創建多個對象");
}
}
//1-設置私有靜態屬性
private static Singleton2 singleton;
//2-提供獲取實例方法,加synchronized關鍵字則可以保證線程安全
public static synchronized Singleton2 getSingleton(){
if(singleton == null){
singleton = new Singleton2();
}
return singleton;
}
}
|
懶漢式:需要加鎖進行同步才能保證線程安全,效率較低,實現了懶加載,在調用的時候纔會校驗是否需要加載,如果已經加載了則直接返回實例對象,否則調用構造器方法進行初始化並返回。
測試代碼:
@Test
public void testSingleton2(){
Singleton2 singleton1 = Singleton2.getSingleton();
Singleton2 singleton2 = Singleton2.getSingleton();
//斷言兩者是同一對象
Assert.assertTrue(singleton1 == singleton2);
}
|
3-雙重校驗方式
/**
* 雙重檢驗鎖方式:在靜態獲取單例實例時做了兩次判斷,且採用synchronized關鍵字確保單例
*/
public class Singleton3 {
//0-私有化構造器
private Singleton3(){}
//1-設置私有靜態屬性,volatile關鍵字保證數據在多個線程間修改是可見的
private volatile static Singleton3 singleton;
//2-提供獲取實例方法,雙重檢驗,加synchronized關鍵字保證線程安全
public static Singleton3 getSingleton(){
if(null == singleton){
synchronized (Singleton3.class){
if(null == singleton){
singleton = new Singleton3();
}
}
}
return singleton;
}
}
|
雙重檢驗方式:採用雙鎖機制,在多線程下也能有較好的性能。
測試代碼:
@Test
public void testSingleton3(){
Singleton3 singleton1 = Singleton3.getSingleton();
Singleton3 singleton2 = Singleton3.getSingleton();
//斷言兩者是同一對象
Assert.assertTrue(singleton1 == singleton2);
}
|
Spring中也是採用這種方式確保bean在容器中的唯一性(必須在<bean>中沒有定義scope的值或者scope屬性爲singleton)代碼如下所示:
DefaultSingletonBeanRegistry.class
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if(singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
Map var4 = this.singletonObjects;
synchronized(this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if(singletonObject == null && allowEarlyReference) {
ObjectFactory singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if(singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject != NULL_OBJECT?singletonObject:null;
}
|
4-靜態內部類方式
/**
* 靜態內部類實現方式:
* 1、外部類沒有static屬性,不會像餓漢式那樣立即加載對象
* 2、只有真正調用getSingleton(),纔會加載靜態內部類,加載類時是線程安全的
* 只能被賦值一次
* 3、兼備了併發高效調用和延遲加載的優勢
*/
public class Singleton4 {
//0-私有化構造器
private Singleton4() {
}
//1-定義一個靜態內部類,持有一個單例實例對象
private static final class Singleton4Holder {
private static final Singleton4 singleton = new Singleton4();
}
//2-獲取單例實例方法
public static Singleton4 getSingleton() {
return Singleton4Holder.singleton;
}
}
|
靜態內部類方式:利用了靜態域延遲初始化,餓漢式是在類加載的時候就初始化實例,但是靜態內部類的方式必須在Singleton4Holder被主動使用的時候纔會加載實例,線程安全,只適用於靜態域的情況。
測試代碼:
@Test
public void testSingleton4(){
Singleton4 singleton1 = Singleton4.getSingleton();
Singleton4 singleton2 = Singleton4.getSingleton();
//斷言兩者是同一對象
Assert.assertTrue(singleton1 == singleton2);
}
|
5-枚舉方式
/**
* 枚舉方式:
*/
public enum Singleton5 {
INSTANCE;
}
|
測試代碼:
@Test
public void testSingleton5(){
Singleton5 singleton1 = Singleton5.INSTANCE;
Singleton5 singleton2 = Singleton5.INSTANCE;
//斷言兩者是同一對象
Assert.assertTrue(singleton1 == singleton2);
}
枚舉方式:實現單例的最佳方式,自動支持序列化機制,絕對防止多次實例化對象,而且可以防止反序列化重新創建對象,從JDK1.5之後纔有enum特性。