二、創(chuàng)建型-單例模式

單例模式是設(shè)計模式中使用最為普遍的模式之一,它是一種對象創(chuàng)建模式,用于產(chǎn)生一個對象具體事例,可以確保系統(tǒng)中一個類只產(chǎn)生一個實(shí)例。在Java中,這樣的行為能帶來兩大好處:①對于頻繁使用的對象可以省略new操作花費(fèi)的時間,這對于那些重量級對象而言是非常可觀的一筆系統(tǒng)開銷②由于new操作的次數(shù)減少,對系統(tǒng)內(nèi)存的使用頻率也會降低,將減輕GC壓力,縮短GC停頓時間。

1、餓漢式
public class SingletonHungry {
    private static SingletonHungry instance= new SingletonHungry();
    private SingletonHungry(){
    }
    public static SingletonHungry getInstance(){
        return instance;
    }
}

該方式性能非常好,getInstance()方法只是簡單的返回instance,沒有任何鎖操作,在并行程序中會有良好的表現(xiàn)。
缺點(diǎn):SingletonHungry實(shí)例在什么時候創(chuàng)建不受控制。對于靜態(tài)成員instance, 它會在類第一次初始化的時候被創(chuàng)建,但是這個時刻并不一定是getInstance()方法第一次被調(diào)用的時候。比方說SingletonHungry中如果還包含一個表示狀態(tài)的靜態(tài)成員STATUS:public static int STATUS = 1, 此時在任何地方引用這個STATUS都會導(dǎo)致instance實(shí)例被創(chuàng)建(任何對SingletonHungry方法或者字段的引用都會導(dǎo)致類初始化,并創(chuàng)建instance實(shí)例,但是類初始化只有一次,因此instance實(shí)例永遠(yuǎn)只會被創(chuàng)建一次),比如外部調(diào)用:System.out.println(SingletonHungry.STATUS),此時即使沒有要求創(chuàng)建單例,new Singleton()也會被調(diào)用。當(dāng)然,如果這個不足在實(shí)際開發(fā)中并不重要,那么這種單例模式也是一種不錯的選擇,它容易實(shí)現(xiàn),代碼易讀且性能優(yōu)越。
如果想精確控制instance創(chuàng)建時間,這種方式就不太友善了,需要一種延遲加載策略,只會在instance第一次使用時創(chuàng)建對象。

2、懶漢式--對象創(chuàng)建的時候使用Synchoronized關(guān)鍵字加鎖
public class SingletonLazySimple {
    private static SingletonLazySimple instance;
    private SingletonLazySimple(){
    }
    public static synchronized SingletonLazySimple getInstance(){
        if (instance == null) {
            instance = new SingletonLazySimple();
        }
        return instance;
    }
}

這種方式的核心思想是我們不需要實(shí)例化instance,當(dāng)getInstance方法第一次被調(diào)用時創(chuàng)建單例對象,為了防止對象被多次創(chuàng)建需要使用synchronized 關(guān)鍵字進(jìn)行方法同步。好處是充分利用了延遲加載,壞處也很明顯:并發(fā)環(huán)境下加鎖競爭激烈的場合對性能產(chǎn)生一定影響。

3、雙重校驗鎖實(shí)現(xiàn)
public class SingletonLazyDoubleCheck {
    private volatile static SingletonLazyDoubleCheck instance = null;
    private SingletonLazyDoubleCheck(){

    }
    public static SingletonLazyDoubleCheck getInstance(){
        if (instance == null) {
            synchronized (SingletonLazyDoubleCheck.class) {
                if (instance == null) {
                    instance = new SingletonLazyDoubleCheck();
                }
            }
        }
        return instance;
    }
}

和2相比,我們在getInstance()方法上增加了兩次非空判斷,并且同步代碼放在一次instance空判斷之后,這樣可以一定程度上減少等待,不至于每一個調(diào)用該方法的線程都會產(chǎn)生競爭。我們注意到這個時候我們在instance變量上增加了volatile關(guān)鍵字,這是防止指令重排序?qū)е挛覀冊趃etInstance拿到的對象可能沒有完全初始化引發(fā)的錯誤。
在getInstance()方法的instance = new SingletonLazyDoubleCheck()中,虛擬機(jī)實(shí)際做了三步工作:
1.給SingletonLazyDoubleCheck實(shí)例分配內(nèi)存;
2.調(diào)用構(gòu)造函數(shù)初始化成員字段;
3.將instance 對象指向分配的內(nèi)存空間(instance 不再為null)。
由于指令重排序,執(zhí)行順序可能是123、132。多線程情況下假如A線程的3執(zhí)行完成,2未執(zhí)行,此時B線程也調(diào)用getInstance()方法,此時判斷instance 不為空,直接返回instance 對象,此時的對象由于可能在構(gòu)造函數(shù)中有一些初始化操作未完成,拿到該對象去操作可能就會導(dǎo)致使用出現(xiàn)一些問題,此時雙重枷鎖就會失效,所以需要給singletonLazyDoubleCheck變量加上volatile關(guān)鍵字防止指令重排序。
這種方式設(shè)計復(fù)雜、丑陋,不推薦使用。

4、內(nèi)部類實(shí)現(xiàn)(最優(yōu))
public class SingletonInnerClass {
    private SingletonInnerClass(){
    }
    public static SingletonInnerClass getInstance(){
        return SingletonHolder.instance;
    }
    private static class SingletonHolder{
        private static SingletonInnerClass instance = new SingletonInnerClass();
    }
}

這種方式的優(yōu)點(diǎn):
1.getInstance()方法中沒有鎖,這使得在高并發(fā)環(huán)境下性能優(yōu)越
2.只有在getInstance()方法第一次被調(diào)用時,SingletonInnerClass的實(shí)例才會被創(chuàng)建,因為這種方法使用了內(nèi)部類和類的初始化方式,內(nèi)部類SingletonHolder被聲明為private,使得不可能在外部訪問并初始化它,而我們只能在getInstance()方法內(nèi)部對SingletonHolder類進(jìn)行初始化,利用虛擬機(jī)的類初始化機(jī)制創(chuàng)建單例。

5、枚舉實(shí)現(xiàn)
public enum  SingletonEnum {
    INSTANCE;

    /**
     * 單利可以有自己的操作
     */
    public void singletonOperation(){
        //功能處理
    }

    public static void main(String[] args) {
        SingletonEnum singletonEnum1 = SingletonEnum.INSTANCE;
        SingletonEnum singletonEnum2 = SingletonEnum.INSTANCE;
        System.out.println(singletonEnum1 == singletonEnum2);
    }
}

優(yōu)點(diǎn):①實(shí)現(xiàn)簡單②枚舉本身就是單例模式,由JVM從根本上提供保障,避免通過反射(即使構(gòu)造函數(shù)被私有了也可以通過反射調(diào)用)和反序列化的漏洞
缺點(diǎn):無延遲加載

6、單例模式的問題

反射 反射可以破解上面幾種(不包含枚舉模式)實(shí)現(xiàn)方式:

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {
    }
    public static synchronized LazySingleton getInstance(){
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}
public static void main(String[] args) throws Exception {
    LazySingleton s1 = LazySingleton.getInstance();
    LazySingleton s2 = LazySingleton.getInstance();
    System.out.println(s1 == s2);
    
    Class<LazySingleton> clazz = (Class<LazySingleton>) Class.forName("com.mobei.lazy.LazySingleton");
    Constructor<LazySingleton> declaredConstructor = clazz.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazySingleton lazySingleton1 = declaredConstructor.newInstance();
    LazySingleton lazySingleton2 = declaredConstructor.newInstance();
    System.out.println(lazySingleton1 == lazySingleton2);
}

解決辦法:可以在構(gòu)造方法中手動拋出異??刂?/p>

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {
        if (instance != null) {
            throw new RuntimeException();
        }
    }
    public static synchronized LazySingleton getInstance(){
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

反序列化 反序列化可以破解上面幾種(不包含枚舉模式)實(shí)現(xiàn)方式:

public class LazySingleton implements Serializable {
    private static LazySingleton instance;
    private LazySingleton() {
        if (instance != null) {
            throw new RuntimeException();
        }
    }
    public static synchronized LazySingleton getInstance(){
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}
public static void main(String[] args) throws Exception {
    LazySingleton s1 = LazySingleton.getInstance();

    FileOutputStream fos = new FileOutputStream("d:/a.txt");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(s1);
    oos.close();
    fos.close();

    FileInputStream fis = new FileInputStream("d:/a.txt");
    ObjectInputStream ois = new ObjectInputStream(fis);
    LazySingleton s2 = (LazySingleton) ois.readObject();
    System.out.println(s2 == s1);
}

解決辦法:可以通過定義readResolve()防止獲得不同對象:反序列化時如果對象所在類定義了readResolve()(實(shí)際是一種回調(diào)),返回此方法指定的對象

public class LazySingleton implements Serializable {
    private static LazySingleton instance;
    private LazySingleton() {
        if (instance != null) {
            throw new RuntimeException();
        }
    }
    public static synchronized LazySingleton getInstance(){
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

    //反序列化時,如果定義了readResolve方法則直接返回此方法指定的對象,而不需要再創(chuàng)建新對象
    private Object readResolve() throws ObjectStreamException {
        return instance;
    }
}
7、效率測試
public static void main(String[] args) throws Exception {
    long start = System.currentTimeMillis();
    int threadNum = 10;
    CountDownLatch latch = new CountDownLatch(threadNum);
    for (int i = 0; i < threadNum; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000000; i++) {
                    Object o = LazySingleton.getInstance();
                }
                latch.countDown();
            }
        }).start();
    }
    latch.await();
    long end = System.currentTimeMillis();
    System.out.println("耗時 : " + (end - start));
}
8、常見應(yīng)用場景

1、項目中讀取配置文件的類,一般不需要每次使用配置文件數(shù)據(jù)都new一個對象去讀取
2、日志應(yīng)用,由于共享的日志文件一直處于打開狀態(tài),因此只能有一個實(shí)例去操作,否則不好追加
3、數(shù)據(jù)庫連接池的設(shè)計
4、Spring中每個Bean默認(rèn)是單例的,優(yōu)點(diǎn)是方便Spring容器管理
5、SpringMVC中控制器對象

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容