七種Java單例模式詳解

博客同步

單例模式作爲常用的設計模式之一,無論是在各種第三方庫還是在我們日常開發中都非常常見,這裏將介紹單例模式七種實現方式。

前提:jvm類加載

class 加載流程: 加載—–驗證—–準備—–解析—–初始化
在class文件中java編譯器會生成一個<clinit>()方法,在初始化階段jvm會調用它,該方法包含了對該類所有的靜態變量的賦值和靜態代碼塊執行操作,並且 jvm保證了<clinit>()方法在多線程的執行環境下安全,因此單例設計模式模式中我們使用靜態變量來存儲實例的引用。

一、單例模式之餓漢式

/**
 * 餓漢式
 */
public class SingletonDemo {

    //當類加載初始化後,就已經完成了實例的創建
    private static SingletonDemo instance = new SingletonDemo();

    /**
     * 不讓外部 new
     */
    private SingletonDemo() {

    }

    public static SingletonDemo getInstance() {
        return instance;
    }

}

餓漢式分析

餓漢式的關鍵使用了靜態變量並且在類加載初始化後就完成了實例的創建,從而保證了多線程環境下實例的一致性,但是它不支持懶加載,當然如果過該類比較輕的話還是可以接受的。

二、單例模式之懶漢式

/**
 * 懶漢式
 */
public class SingletonDemo {
    
    //僅僅定義靜態變量
    private static SingletonDemo instance = null;

    /**
     * 不讓外部 new
     */
    private SingletonDemo() {

    }

    public static SingletonDemo getInstance() {
        
        return instance!=null?instance:new SingletonDemo();
    }

}

懶漢式分析

懶漢式僅僅在在使用實例的時候纔去創建實例的對象,在多線程環境下,實例可能會創建多次,線程不安全,但是它支持懶加載。

三、線程安全懶漢式

/**
 * 線程安全懶漢式
 */
public class SingletonDemo {

    //僅僅定義靜態變量
    private static SingletonDemo instance = null;

    /**
     * 不讓外部 new
     */
    private SingletonDemo() {

    }

    //使用synchronized 同步關鍵字,保證該方法多線程下只能單個線程使用
    public synchronized static SingletonDemo getInstance() {

        return instance!=null?instance:new SingletonDemo();
    }

}

線程安全懶漢式分析

通過synchronized 關鍵字修飾 getInstance方法實現線程安全,但是同一時刻只能有一個線程訪問,性能較低。

四、單例模式之雙重檢驗

/**
 * 雙重檢驗模式
 */
public class SingletonDemo {

    //僅僅定義靜態變量
    private static SingletonDemo instance = null;

    /**
     * 不讓外部 new
     */
    private SingletonDemo() {

    }

    public static SingletonDemo getInstance() {
        
        if (instance==null){
            
            //同步代碼塊
            synchronized (SingletonDemo.class){
                if (instance == null) {
                    instance = new SingletonDemo();
                }
            }
            
        }
        return instance;
    }

}

雙重檢驗分析

這種模式可能會引起空指針異常,new 一個對象需要經歷:分配內存空間-初始化-引用賦值(指向對象的地址)過程,而且java中存在cpu指令重排序,因此在多線程環境下線程一執行 可能執行new對象:分配內存空間-引用賦值-初始化,當執行到引用賦值時,線程二會看到instance!=null,但這時,對象並未完成初始化,對象是空的,直接使用會空指針異常。因此這種方式也是線程不安全的。

volatile雙重檢驗

/**
 * volatile雙重檢驗模式
 */
public class SingletonDemo {

    //僅僅定義靜態變量
    private volatile static SingletonDemo instance = null;

    /**
     * 不讓外部 new
     */
    private SingletonDemo() {

    }

    public static SingletonDemo getInstance() {

        if (instance==null){

            //同步代碼塊
            synchronized (SingletonDemo.class){
                if (instance == null) {
                    instance = new SingletonDemo();
                }
            }

        }
        return instance;
    }

}

volatile雙重檢驗分析

volatile 在這的作用就是禁止指令重排序,從而使對象的創建嚴格按照分配內存空間-初始化-引用賦值(指向對象的地址)過程,實現了線程安全。

靜態內部類模式

/**
 * 靜態內部類模式
 */
public class SingletonDemo {

    

    /**
     * 不讓外部 new
     */
    private SingletonDemo() {

    }
    

    public static SingletonDemo getInstance() {
        return Holder.instance;
    }
    
    //使用靜態內部類來初始化instance
    private static class Holder{
        
        //當類加載初始化後,就已經完成了實例的創建
        private static SingletonDemo instance = new SingletonDemo();
        
    }

}

靜態內部類模式分析

靜態內部類模式歸根到底是<clinit>()同步方法帶來的結果,這種模式實現了線程安全和懶加載,被公認爲最好的單例模式之一。

枚舉模式

/**
 * 枚舉模式
 */
public class SingletonDemo {



    /**
     * 不讓外部 new
     */
    private SingletonDemo() {

    }


    public static SingletonDemo getInstance() {
        return Holder.INSTANCE.instance;
    }

    //使用枚舉
    private  enum  Holder{

        INSTANCE;
        private SingletonDemo instance;
        
        Holder(){
            instance = new SingletonDemo();
        }

    }

}

枚舉模式分析

主要利用了enum的特性,保證線程安全。

總結

各種模式各有有缺點,但是對懶加載和高性能有要求的,還是用靜態內部類類模式、volatile雙重檢驗模式、枚舉模式,既能滿足線程安全,還能滿足性能要求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章