單例模式

單例模式

要點:

  • 一是某個類只能有一個實例
    • 構造器私有化
  • 二是它必須自行創建這個實例
    • 含有一個該類的靜態變量來保存這個唯一的實例
  • 三是它必須自行向整個系統提供這個實例
    • 對外提供獲取該實例對象的方法
    • (1)直接暴露
    • (2)用靜態變量的get方法獲取

幾種常見的形式

  • 餓漢式:直接創建對象,不存在線程安全的問題

    • 直接實例化餓漢式(簡潔直觀)

      package singletondemo;
      
      /**
       * 餓漢式:
       * 在類初始化時直接創建實例對象,不管是否需要這個對象都會創建
       * (1)構造器私有化
       * (2)自行創建,並且用靜態變量保存
       * (3)向外提供這個實例
       * (4)強調這是一個單例,我們可以用final修改
       */
      public class Singleton1 {
      	public static final Singleton1 INSTANCE = new Singleton1();
      	private Singleton1() {
      		
      	}
      }
      
      
      // 備註:可以直接通過類名.來訪問
      
    • 枚舉式(最簡潔)

      package singletondemo;
      
      /**
       * 枚舉類型:表示該類型的對象是有限的幾個
       * 我們可以限定爲一個,就成了單例
       */
      public enum Singleton2 {
      	INSTANCE
      }
      
      // 備註:可以通過類名.來訪問
      
    • 靜態代碼塊餓漢式(適合複雜實例化)

      package singletondemo;
      
      public class Singleton3 {
      	public static final Singleton3 INSTANCE;
      	static{
      		INSTANCE = new Singleton3();
      	}
      	private Singleton3() {
      		
      	}
      }
      // 備註:這個跟Singletion1是等效的,而且更復雜,試用於複雜的實例化
      
      package singletondemo;
      
      import java.io.IOException;
      import java.util.Properties;
      
      public class Singleton3 {
      	public static final Singleton3 INSTANCE;
      	private String info;
      	static{
      		try {
      			Properties pro = new Properties();
      			// 假設在src下有一個singleton.properties配置文件
      			// 文件中有個info屬性
      			pro.load(Singleton3.class.getClassLoader().
      					getResourceAsStream("singleton.properties"));
      			INSTANCE = new Singleton3(pro.getProperty("info"));
      		} catch(IOException e) {
      			throw new RuntimeException(e);
      		}
      	}
      	private Singleton3(String info) {
      		this.info = info;
      	}
      	// 添加get set方法,toString()方法進行測試驗證
      }
      
      // 直接通過類名.來訪問,輸出其get方法可得到配置文件中info的屬性值
      
  • 懶漢式:延遲創建對象

    • 線程不安全(適用於單線程)

      package singletondemo;
      
      /**
       * 懶漢式:
       *   延遲創建這個實例對象
       * (1)構造器私有化
       * (2)用一個靜態變量保存這個唯一的實例
       * (3)提供一個靜態方法,獲取這個實例對象
       *
       */
      public class Singleton4 {
      	private static Singleton4 instance;
      	private Singleton4() {
      		
      	}
      	public static Singleton4 getInstance() {
      		if(instance == null) {
                  // Thread.sleep(100)
      			instance = new Singleton4();
      		}
      		return instance;
      	}
      }
      
      /*
       * 這段代碼是有可能發生線程安全問題的
       * 當第一個線程進來,不是null,到了休眠時間
       * 此時第二個線程進來,不是null,休眠
       * 線程一結束休眠,new 了一個對象
       * 線程二結束休眠,new 了一個對象
       * 所以,此時,兩個對象是不一樣的
       */
      

      測試

      package test;
      
      import singletondemo.Singleton4;
      
      public class Singleton4Test {
      	public static void main(String[] args) {
      		Singleton4 s1 = Singleton4.getInstance();
      		Singleton4 s2 = Singleton4.getInstance();
      		System.out.println(s1 == s2);
      		System.out.println(s1);
      		System.out.println(s2);
      	}
      }
      
      // 輸出結果如下:
      // true
      // singletondemo.Singleton4@15db9742
      // singletondemo.Singleton4@15db9742
      
    • 線程安全(適用於多線程)

      package singletondemo;
      
      /**
       * 懶漢式:
       *   延遲創建這個實例對象
       * (1)構造器私有化
       * (2)用一個靜態變量保存這個唯一的實例
       * (3)提供一個靜態方法,獲取這個實例對象
       *
       */
      public class Singleton5 {
      	private static Singleton5 instance;
      	private Singleton5() {
      		
      	}
      	public static Singleton5 getInstance() {
      		if(instance == null) {
      			synchronized (Singleton5.class) {
      				if(instance == null) {
      					// 休眠測試Thread.sleep(200)
      					instance = new Singleton5();
      				}
      			}
      		}
      		return instance;
      	}
      }
      
      
    • 靜態內部類形式(適用於多線程)

      package singletondemo;
      
      /**
       * 在內部類被加載和初始化時,才創建INSTANCE實例對象
       * 靜態內部類不會自動隨着外部類的加載和初始化而初始化,它是要單獨去加載和初始化的
       * 因爲是在 內部類加載和初始化時創建的,因此是線程安全的
       */
      public class Singleton6 {
      	private Singleton6() {
      		
      	}
      	private static class Inner{
      		private static final Singleton6 INSTANCE = new Singleton6();
      	}
      	public static Singleton6 getInstance() {
      		return Inner.INSTANCE;
      	}
      }
      
      

總結

  • 如果是餓漢式,枚舉形式最簡單
  • 如果是懶漢式,靜態內部類形式最簡單
  • 注意線程安全問題
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章