深入理解設計模式之單例模式

單例模式看着一篇就夠了
問題:
多個線程操作不同實例對象,現在需要多個線程要操作同一對象,要保證對象的唯一性。
解決問題:
實例化過程中只實例化一次

單例模式屬於創造型模式,是常用的設計模式之一。

單例模式(Singleton),保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

如何區分餓漢式和懶漢式:在自己被加載時就將自己實例化,稱之爲餓漢式單例類,而要是在第一次被引用時,纔會將自己實例化,稱之爲懶漢式。

餓漢式

線程安全:在加載的時候(ClassLoader) 已經被實例化,所以只有一次,線程安全的。
懶加載:沒有延遲加載,對於長時間不使用,影響性能,如果佔的內存多會導致內存溢出
性能:比較好

package sheji;

/**
 * @author zhr
 * @create 2018-11-15 下午4:02
 */
public class Singleton {
	//static 在加載的時候就產生了實例對象
    private static Singleton instance = new Singleton();

    private Singleton(){

    }

    public static Singleton getInstance(){
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("這兩個對象是相同的實例");
        }
    }
}
控制檯打印:這兩個對象是相同的實例

懶漢式

線程安全:不能保證實例對象的唯一性,可能出現多個線程同時進入到instance==null中 生成多個對象(一票否決不要用)
懶加載:有延遲加載
性能:好
跟餓漢式比:優化了在調用過程的時候纔去實例化,但又出現了新的問題那就是線程安全的問題


/**
 * @author zhr
 * @create 2018-11-15 下午3:59
 */
public class Singleton {

    private static Singleton instance;

    private Singleton(){

    }

    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("這兩個對象是相同的實例");
        }
    }
}

控制檯打印:這兩個對象是相同的實例

懶漢式+同步方法

線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:不好,因爲使用了synchronized 多個線程訪問的會後就會蛻化到了串行執行
跟懶漢式比:線程安全了,但是鎖的粒度太大性能損失很大


/**
 * @author zhr
 * @create 2018-11-15 下午4:00
 */
public class Singleton {

    private static Singleton instance;

    private Singleton(){

    }

    public synchronized static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("這兩個對象是相同的實例");
        }
    }
}

控制檯打印:這兩個對象是相同的實例

懶漢式+同步代碼塊

線程安全:不能保證實例唯一
懶加載:有延遲加載
性能:一般
懶漢式+同步方法比較:我們鎖的粒度雖然減小了,但是在併發的環境下當,多個線程同時進入到instance==null的時候第一個實例創建對象,釋放鎖的時候第二個線程可能就會創建對象,從而導致線程不安全


/**
 * @author zhr
 * @create 2018-11-15 下午4:07
 */
public class Singleton {

    private static Singleton instance;

    private Singleton(){

    }

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

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("這兩個對象是相同的實例");
        }
    }
}
控制檯打印:這兩個對象是相同的實例

雙重校驗鎖 DCL

線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:比較好
問題:因爲happens-before指令重排,裏面有多個引用對象需要初始化的時候,會出現空指針異常


/**
 * @author zhr
 * @create 2019-09-14 下午3:08
 */
public class Singleton {

    private static Singleton instance;
    Connection conn;
    Socket socket;

    private Singleton(){
		//初始化conn 和 socket 這裏指令重排 把初始化放在前面了,導致第一個線程初始化了,第二個線程進來認爲已經初始化了,然後獲取了conn就會出現空指針異常
    }

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

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("這兩個對象是相同的實例");
        }
    }
}
控制檯打印:這兩個對象是相同的實例

Volatile+雙重校驗鎖 DCL

線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:比較好(推薦使用)
解決DCL指令重排問題方案是機上volatile使初始化前的東西不能被指令重排就可以了。


/**
 * @author zhr
 * @create 2019-09-14 下午3:10
 */
public class Singleton {

    private volatile static Singleton instance;
    Connection conn;
    Socket socket;

    private Singleton(){

    }

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

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        if(s1 == s2){
            System.out.println("這兩個對象是相同的實例");
        }
    }
}
控制檯打印:這兩個對象是相同的實例

Holder持有者

聲明類的時候,成員變量中不聲明實例變量,而放到內部靜態類中,在內部靜態類中我們提供實例化這個類,主要是爲了避免加鎖,只有主動使用的時候纔會去進行實例化
線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:比較好(廣泛使用)


/**
 * @author zhr
 * @create 2019-09-14 下午3:20
 */
public class HolderDemo{

	//可以刪除
    private HolderDemo(){

    }
    
	private static class Holder{
		//懶加載 沒加鎖
		private static HolderDemo instance = new HolderDemo();
		
	}

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

}
控制檯打印:這兩個對象是相同的實例

枚舉

線程安全:可以保證實例唯一性
懶加載:沒有延遲加載
性能:比較好(廣泛使用)
評價:,其實跟餓漢式很像,代碼簡潔優雅


/**
 * @author zhr
 * @create 2019-09-14 下午3:25
 */
public enum EnumSingleton{
	//常量,在加載的時候被實例化一次
	INSTANCE;

	public static EnumSingleton getInstance(){
		return INSTANCE;
	}

}

枚舉+Holder

線程安全:可以保證實例唯一性
懶加載:有延遲加載
性能:比較好(廣泛使用)


/**
 * @author zhr
 * @create 2019-09-14 下午3:30
 */
public class EnumSingletonDemo{

	private EnumSingletonDemo(){

	}
	
	//靜態內部類 只有在使用的時候纔會加載實例化
	private enum EnumHolder{
		//常量,在加載的時候被實例化一次,方法區
		INSTANCE;

		private EnumSingletonDemo instance;
		
		EnumHolder(){	
			this.instance = new EnumSingletonDemo();
		}

		private EnumSingletonDemo getInstance(){
			return instance;
		}
	}//懶加載

	public static EnumSingletonDemo getInstance(){
		return EnumHolder.INSTANCE.getInstance();
		//return  EnumHolder.INSTANCE.instance;
	}
}

如果看完感覺受益匪淺,可以幫忙點點關注,點點贊,還會持續更新好的作品。

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