Java設計模式(一)單例模式

單例設計模式

什麼是單例

保證一個類只有一個實例,並且提供一個訪問該全局訪問點

單例應用場景

  1. Windows的Task Manager(任務管理器)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能打開兩個windows task manager嗎? 不信你自己試試看哦~

  2. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程中,回收站一直維護着僅有的一個實例。

  3. 網站的計數器,一般也是採用單例模式實現,否則難以同步。

  4. 應用程序的日誌應用,一般都何用單例模式實現,這一般是由於共享的日誌文件一直處於打開狀態,因爲只能有一個實例去操作,否則內容不好追加。

  5. Web應用的配置對象的讀取,一般也應用單例模式,這個是由於配置文件是共享的資源。

  6. 數據庫連接池的設計一般也是採用單例模式,因爲數據庫連接是一種數據庫資源。數據庫軟件系統中使用數據庫連接池,主要是節省打開或者關閉數據庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因爲何用單例模式來維護,就可以大大降低這種損耗。

  7. 多線程的線程池的設計一般也是採用單例模式,這是由於線程池要方便對池中的線程進行控制。

  8. 操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。

  9. HttpApplication 也是單位例的典型應用。熟悉ASP.Net(IIS)的整個請求生命週期的人應該知道HttpApplication也是單例模式,所有的HttpModule都共享一個HttpApplication實例.

單例優缺點

優點:

  1. 在單例模式中,活動的單例只有一個實例,對單例類的所有實例化得到的都是相同的一個實例。這樣就 防止其它對象對自己的實例化,確保所有的對象都訪問一個實例

  2. 單例模式具有一定的伸縮性,類自己來控制實例化進程,類就在改變實例化進程上有相應的伸縮性。

  3. 提供了對唯一實例的受控訪問。

  4. 由於在系統內存中只存在一個對象,因此可以 節約系統資源,當 需要頻繁創建和銷燬的對象時單例模式無疑可以提高系統的性能。

  5. 允許可變數目的實例。

  6. 避免對共享資源的多重佔用。

缺點:

  1. 不適用於變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態。

  2. 由於單利模式中沒有抽象層,因此單例類的擴展有很大的困難。

  3. 單例類的職責過重,在一定程度上違背了“單一職責原則”。

  4. 濫用單例將帶來一些負面問題,如爲了節省資源將數據庫連接池對象設計爲的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認爲是垃圾而被回收,這將導致對象狀態的丟失。

單例創建方式

  1. 餓漢式:類初始化時,會立即加載該對象,線程天生安全,調用效率高。

  2. 懶漢式: 類初始化時,不會初始化該對象,真正需要使用的時候纔會創建該對象,具備懶加載功能。

  3. 靜態內部方式:結合了懶漢式和餓漢式各自的優點,真正需要對象的時候纔會加載,加載類是線程安全的。

  4. 枚舉單例: 使用枚舉實現單例模式 優點:實現簡單、調用效率高,枚舉本身就是單例,由jvm從根本上提供保障!避免通過反射和反序列化的漏洞, 缺點沒有延遲加載。

  5. 雙重檢測鎖方式 (因爲JVM本質重排序的原因,可能會初始化多次,不推薦使用)

餓漢式

  1. 類初始化時創建對象,不管需不需要實例對象,都會創建
  2. 不存在線程安全問題,因爲實例是在類創建和初始化時創建,是由類加載器完成的,類加載器是線程安全的

書寫
(1)構造器私有化
(2)自行創建,並且用靜態變量保存
(3)向外提供這個實例
(4)強調這是一個靜態的,可以使用final修飾

// 簡潔直觀
public class Singleton1 {
    private Singleton1(){}
    public static final Singleton1 INSTANCE = new Singleton1();
}

枚舉式

// 使用枚舉實現單例模式 優點:實現簡單、枚舉本身就是單例,由jvm從根本上提供保障!避免通過反射和反序列化的漏洞 缺點沒有延遲加載
public class User {
	public static User getInstance() {
		return SingletonDemo04.INSTANCE.getInstance();
	}

	private static enum SingletonDemo04 {
		INSTANCE;
		// 枚舉元素爲單例
		private User user;

		private SingletonDemo04() {
			System.out.println("SingletonDemo04");
			user = new User();
		}

		public User getInstance() {
			return user;
		}
	}

	public static void main(String[] args) {
		User u1 = User.getInstance();
		User u2 = User.getInstance();
		System.out.println(u1 == u2);
	}
}

靜態代碼塊方式

// 適合複雜的實例化
class A {
    static final String info = "password";
}
public class Singleton3_test {
    public String info;

    private Singleton3(String info) {
        this.info = info;
    }
    static {
        // 從其他地方獲取的值
        String val = A.info;
        INSTANCE = new Singleton3_test(val);
    }

    public static final Singleton3_test INSTANCE;

    @Override
    public String toString() {
        return "Singleton3{" +
                "info='" + info + '\'' +
                '}';
    }
    public String getInfo() {
        return info;
    }
    public void setInfo(String info) {
        this.info = info;
    }
}

懶漢式

延遲創建實例對象,需要時纔去創建對象,否則不創建

書寫
(1)構造器私有化
(2)用一個靜態變量保存這個唯一的實例
(3)提供一個靜態方法,獲取這個實例對象

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

// 測試
public class Lan_Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<SingletonLan1> call = new Callable<SingletonLan1>() {
            @Override
            public SingletonLan1 call() throws Exception {
                return SingletonLan1.getInstance();
            }
        };

        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<SingletonLan1> f1 = es.submit(call);
        Future<SingletonLan1> f2 = es.submit(call);
        SingletonLan1 s1 = f1.get();
        SingletonLan1 s2 = f2.get();
        es.shutdown();

        System.out.println(s1 == s2);
        System.out.println(s1);
        System.out.println(s2);
    }
}

雙重檢測鎖方式

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

// 測試
public class Lan_Test2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<SingletonLan2> call = new Callable<SingletonLan2>() {
            @Override
            public SingletonLan2 call() throws Exception {
                return SingletonLan2.getInstance();
            }
        };

        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<SingletonLan2> f1 = es.submit(call);
        Future<SingletonLan2> f2 = es.submit(call);
        SingletonLan2 s1 = f1.get();
        SingletonLan2 s2 = f2.get();
        es.shutdown();

        System.out.println(s1 == s2);
        System.out.println(s1);
        System.out.println(s2);
    }
}

靜態內部類方式

  1. 在內部類被加載和創建時,纔去創建實例對象
  2. 靜態內部類不會隨着外部類的加載和初始化而初始化,它是要單獨加載和初始化的
  3. 因爲是內部類創建和初始化時,創建的對象,所以不存在線程安全問題
// 相比上一種方法,簡潔許多
public class SingletonLan3 {
    private SingletonLan3(){}

    private static class Inner {
        private static final SingletonLan3 INSTANCE = new SingletonLan3();
    }

    public static SingletonLan3 getInstance(){
        return Inner.INSTANCE;
    }
}

單例防止反射漏洞攻擊

// 在構造函數中,只能允許初始化化一次即可。
public SingletonDemo04 {
	private static boolean flag = false;
	private SingletonDemo04() {
		if (flag == false) {
			flag = !flag;
		} else {
			throw new RuntimeException("單例模式被侵犯!");
		}
	}

	public static void main(String[] args) {
	}
}

如何選擇單例創建方式

  1. 如果不需要延遲加載單例,可以使用枚舉或者餓漢式,相對來說枚舉性好於餓漢式。
  2. 如果需要延遲加載,可以使用靜態內部類或者懶漢式,相對來說靜態內部類好於懶漢式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章