單例模式詳解一

單列模式的應用場景

  • 單例模式(Singleton Pattern)是指確保一個類在任何情況下都絕對只有一個實例,並提供一個全局訪問點。單例模式是創建新模式。J2EE標準中的ServletContext、ServletContextConfig 等、Spring 框架應用中的ApplictionContext、數據庫的連接池等都是單例形式。

餓漢式單例模式

  • 先來看單例模式的類結構圖,如圖

  • 餓漢式單例模式在類加載的時候立即初始化,並且創建單例對象。它是絕對線程安全,在線程還沒出現以前就實例化了,不可能存在線程安全問題。

    優點: 沒有加任何鎖、執行效率比較高,用戶體驗比懶漢式單例模式好。

    缺點: 類加載的時候就初始化,不管用不用都佔用空間,浪費了內存。

  • Spring 中Ioc 容器ApplicationContext本身就是典型的餓漢式單例模式,接下來看一段代碼。

public class HungrySingleton {

    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();

    private HungrySingleton(){

    }
    public static HungrySingleton getInstance(){
        return HUNGRY_SINGLETON;
    }
}
  • 還有另一種寫法靜態代碼塊機制
public class HungryStaticSingleton {
    private static final HungryStaticSingleton HUNGRY_SINGLETON;

    static {
        HUNGRY_SINGLETON = new HungryStaticSingleton();
    }
    private HungryStaticSingleton(){

    }

    public static HungryStaticSingleton getInstance(){
        return HUNGRY_SINGLETON;
    }
}
  • 這兩種寫法都非常簡單,也非常好理解,餓漢式單例模式適用於單例對象比較少的情況,下面我們看性能更優的寫法。

懶漢式單例模式

  • 懶漢式單例模式的特點是:被外部類調用的時候內部類纔會加載。下面看懶漢模式的簡單實現
//懶漢式單例模式在外部需要使用的時候才進行實例化
public class LazySimpleSingleton {
    private static  LazySimpleSingleton LAZY_SIMPLE_SINGLETON = null;
    private LazySimpleSingleton(){}
    public static LazySimpleSingleton getInstance(){
        if(LAZY_SIMPLE_SINGLETON == null){
            LAZY_SIMPLE_SINGLETON = new LazySimpleSingleton();
        }
        return LAZY_SIMPLE_SINGLETON;
    }
}

  • 然後我們寫一個線程類
public class ExectorThread implements Runnable {
    @Override
    public void run() {
        LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance();

        System.out.println(Thread.currentThread().getName()+":"+ lazySimpleSingleton);
    }
}

  • 測試代碼
public class LazySimpleSingletonTest {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new ExectorThread());
        Thread thread2 = new Thread(new ExectorThread());
        thread1.start();
        thread2.start();
    }
}
  • 運行結果:

  • 上面的代碼一定概率出現兩種不同的結果,意味着上面的單例模式存在安全隱患。那麼我們怎樣優化代碼才能使得懶漢式單例模式線程安全呢?來看代碼我們在getInstance()方法加上synchronized關鍵字,使這個方法變成線程同步方法。
public class LazySimpleSingleton {

    private static  LazySimpleSingleton lazy = null;

    private LazySimpleSingleton(){}
    public synchronized static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy =  new LazySimpleSingleton();
        }
        return lazy;
    }
}

  • 雖然線程安全問題解決了,但是用synchronized加鎖時,在線程數量比較多的情況下,如果cpu分配壓力上升,則會導致線程阻塞,性能大幅下降,我們引入一種新的方式,雙重加鎖
public class LazySimpleSingleton {

    private volatile static  LazySimpleSingleton lazy = null;

    private LazySimpleSingleton(){}
    public  static LazySimpleSingleton getInstance(){
        if(lazy == null){
            synchronized (LazySimpleSingleton.class){
                if(lazy == null){
                    lazy =  new LazySimpleSingleton();
                }
            }
        }
        return lazy;
    }
}
  • 優化後的結果是代碼塊的阻塞,邏輯不復雜的情況下感知較小。但是用到synchronized 關鍵字總歸要上鎖,看下邊一種寫法,採用靜態內部類:
//這種形式兼顧了餓漢式單例模式的內存浪費問題 和sychronized 的性能問題
public class LazyInnerClassSingleton {
    //使用LazyInnerClassSingleton的時候,默認會先初始化內部類
    //入股沒使用則內部類是不加載的
    private LazyInnerClassSingleton(){}
    //static 關鍵字是爲了使單例的空間共享,不會被重寫,重載
    public static  final LazyInnerClassSingleton getInstance (){
        return LazyHolder.LAZY;
    }
    //默認不加載
    private static class LazyHolder{
        private static  final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }

}
  • 這種方式兼顧了餓漢式單例模式的內存浪費問題和synchronized的性能問題,內部類一定要在方法調用之前初始化,巧妙的避免了線程安全問題。
  • 下期:單例模式詳解二 以及更優的單例模式。謝謝。
發佈了6 篇原創文章 · 獲贊 8 · 訪問量 3152
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章