引言
單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。
這種模式涉及到一個單一的類,該類負(fù)責(zé)創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
設(shè)計原則:
1、單例類只能有一個實例,所以這個示例必須私有并且只能創(chuàng)建一次;
2、單例類必須自己創(chuàng)建自己的唯一實例,所以它的構(gòu)造方法必須私有;
3、單例類必須給所有其他對象提供這一實例,提供對外的返回示例的方法,這個方法只能是靜態(tài)方法,因為別人無法創(chuàng)建實例,從而這個實例對象也為靜態(tài)屬性。
UML圖如下:

優(yōu)缺點分析
1.優(yōu)點:
1>內(nèi)存中僅此一份,減少了內(nèi)存的開銷,尤其是頻繁的創(chuàng)建和銷毀實例,如頁面緩存;
2>避免對資源的多重占用,如多線程對文件的寫操作;
2.缺點:沒有接口,不能繼承,擴(kuò)展性較差。
單例模式有多種寫法,這里我們學(xué)習(xí)其中的6中方法:
餓漢式(線程安全)
package com.qicode.kakaxicm.designpattern.singleton;
/**
* Created by chenming on 2018/6/11
* 餓漢式,線程安全
*/
public class HungrySingleton {
private static HungrySingleton sInstance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getsInstance() {
return sInstance;
}
public void showMsg(String s) {
System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
}
}
這種方式在類加載時就完成了初始化,類加載較慢,單獲取對象速度快。它基于類加載機(jī)制,避免了線程安全問題。如果始終沒用到這個實例,則會造成內(nèi)存浪費。
懶漢式(線程不安全)
package com.qicode.kakaxicm.designpattern.singleton;
/**
* Created by chenming on 2018/6/11
*/
public class LazySingleton {
private static LazySingleton sInstance;
private LazySingleton() {
}
public static LazySingleton getsInstance(){
if(sInstance == null){
sInstance = new LazySingleton();
}
return sInstance;
}
public void showMsg(String s) {
System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
}
}
這種方式在首次使用的時候才構(gòu)造實例,節(jié)約資源,多線程不安全。
懶漢式(線程安全)
package com.qicode.kakaxicm.designpattern.singleton;
/**
* Created by chenming on 2018/6/11
*/
public class LazySingleton {
private static LazySingleton sInstance;
private LazySingleton() {
}
public static synchronized LazySingleton getsInstance(){
if(sInstance == null){
sInstance = new LazySingleton();
}
return sInstance;
}
public void showMsg(String s) {
System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
}
}
在懶漢式的基礎(chǔ)上,對getsInstance加了同步,解決線程安全問題,但是大部分情況下,用不到同步,影響效率,所以不建議這種方式。
雙重鎖(DCL)
package com.qicode.kakaxicm.designpattern.singleton;
/**
* Created by chenming on 2018/6/11
*/
public class DCLSingleton {
private static DCLSingleton sInstance;
private DCLSingleton() {
}
public static DCLSingleton getsInstance() {
if (sInstance == null) {
synchronized (DCLSingleton.class) {
if(sInstance == null){
sInstance = new DCLSingleton();
}
}
}
return sInstance;
}
public void showMsg(String s) {
System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
}
}
第一次判空是為了避免不必要的同步,第二次的判空和構(gòu)建實例為原子操作,解決了多線程安全問題。這種方法只在第一次使用時稍慢,整體效率高,也解決了線程安全問題。不過還有比這更好的方法,那就是靜態(tài)內(nèi)部類。
靜態(tài)內(nèi)部類單例模式
package com.qicode.kakaxicm.designpattern.singleton;
/**
* Created by chenming on 2018/6/11
*/
public class Singleton {
private Singleton(){
}
private static class SingletonHolder{
private static final Singleton sInstance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
public void showMsg(String s) {
System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
}
}
靜態(tài)內(nèi)部類在Singleton加載時,并不會加載SingletonHolder類,因此實現(xiàn)懶加載,只有g(shù)etInstance被調(diào)用時,SingletonHolder才會被加載,此時實例才被創(chuàng)建,而且因為類只被加載一次,所以也不存在線程安全問題。
枚舉法
public enum Singleton {
INSTANCE;
public void showMsg(String s) {
System.out.println(this.getClass().getSimpleName() + "--" + this + "--" + s);
}
}
枚舉實例的創(chuàng)建線程安全,并且在任何情況下都是單例。枚舉雖然簡單,但是因為可讀性的原因沒被推廣開來。
關(guān)于反射和反序列化的單例破壞問題
1.反射可以強(qiáng)制獲取構(gòu)造器,創(chuàng)建實例,可以在構(gòu)造器里面添加標(biāo)記,當(dāng)?shù)诙伪徽{(diào)用時拋出異常解決。
2.反序列化可可以創(chuàng)建新的對象,它提供了readResolve可以控制對象的反序列化:
private Object readResolve(){
return instance;
}
最后,說明一下這些方式的應(yīng)用場景。一般情況下,不建議使用第 1 種和第 2 種懶漢方式,建議使用第 3 種餓漢方式。只有在要明確實現(xiàn) lazy loading 效果時,才會使用第 5 種登記方式。如果涉及到反序列化創(chuàng)建對象時,可以嘗試使用第 6 種枚舉方式。如果有其他特殊的需求,可以考慮使用第 4 種雙檢鎖方式。
完整代碼地址:設(shè)計模式學(xué)習(xí)GayHub地址