設計模式之單例模式

寫在最前面

單例的文章很多,所以本文主要想記錄下幾種常用單例的不好理解之處

  • 雙重檢測 靜態資源上的volatile 關鍵字作用 ?
  • 靜態內部類怎麼實現線程安全單例 ?
  • 枚舉單例怎麼理解 ?

單例的概念

  • 單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。通過單例模式可以保證系統中,應用該模式的一個類只有一個實例。即一個類只有一個對象實例。

  • Java中單例模式定義:“一個類有且僅有一個實例,並且自行實例化向整個系統提供。”
    詳情見百度百科

單例的特點

  • 一是某個類只能有一個實例
  • 二是它必須自行創建這個實例
  • 三是它必須自行向整個系統提供這個實例
    詳情見百度百科

單例的幾種寫法

  • 餓漢模式 (線程安全 低效)
/**
 * 類加載時就創建實例
 */
public class Singleton {
	private Singleton(){}
    private final static Singleton singleton= new Singleton();
    public static Singleton getInstance(){
        return singleton;
    }
}
/**
 * 靜態代碼塊實現(類加載時)
 */
public class Singleton {
	private Singleton() {}
    private static Singleton singleton;
    static {
        singleton= new Singleton();
    }
    public static Singleton getInstance() {
        return singleton;
    }
}
  • 懶漢模式 (線程不安全)
 public class Singleton {
 	private Singleton(){}
    private static Singleton singleton;
    public static Singleton getInstance() {
        if (null == singleton) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

  • 懶漢加鎖 (線程安全 低效)
/**
 * 方法加鎖
 */
public class Singleton {
	private Singleton() {}
    private static Singleton singleton;
    public static synchronized Singleton getInstance() {
        if (null == singleton) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
/**
 * 代碼塊加鎖
 */
public class Singleton {
	private Singleton() {}
    private static Singleton singleton;
    public static Singleton getInstance() {
        if (null == singleton) {
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}
  • 雙重檢測 (線程安全 高效)

      創建實例代碼       singleton = new Singleton();
      主要做了三件事     (1.開闢空間   2.創建對象  3.地址指向)
      1>  不加volatile:可能執行順序爲132 這時第二個獲取資源會是地址不爲空但是對象實例爲空
      2>  加上volatile:防止JVM指令重排 確保執行順序爲123 確保要麼靜態變量爲NULL 要麼有地址指向並且對象實例也不爲空
    
public class Singleton {
	private Singleton() {}
    private static volatile Singleton singleton;
    public static Singleton getInstance() {
        if (null == singleton) {
            synchronized (Singleton.class) {
                if (null == singleton) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
  • 靜態內部類 (線程安全 高效)

      靜態內部類是怎麼保證線程安全的呢? 
      虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確地加鎖、同步,如果多個線程同時去初始化一個類,那麼只會有
      一個線程去執行這個類的<clinit>()方法,其他線程都需要阻塞等待,直到活動線程執行<clinit>()方法完畢。如果在一個類的
      <clinit>()方法中有耗時很長的操作,就可能造成多個進程阻塞(需要注意的是,其他線程雖然會被阻塞,但如果執行<clinit>()
      方法後,其他線程喚醒之後不會再次進入<clinit>()方法。同一個加載器下,一個類型只會初始化一次。)
      在實際應用中,這種阻塞往往是很隱蔽的
    
public class Singleton {
    private Singleton() {}
    private static class SingletonInner {
        private static final Singleton singleton= new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonInner.singleton;
    }
}
  • 枚舉 (線程安全 高效)

     枚舉實現單例可能不太好理解
     其實在編譯成class文件時,會默認爲枚舉類成員加上 final static修飾
     再想想就和靜態內部類的實現幾乎一樣了
     在實際代碼中可以將需要執行一次的可寫在私有方法中
    
public enum Singleton {
    INSTANCE;
    public void methodA() {
    }
    // 枚舉中私有方法只會執行一次
    private void methodB() {
    }
}

參考文章

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