Define Single ton class in Java
  • when you make a constructor of a class private, that particular class can generate only one object. This type of class is known as singleton class.

# Singleton 單例設計模式

  • 整個類裡只能有一個實例可以被獲取或使用。
    e.g. 代表 JVM 運行環境的 Runtime class

# 要點

  1. 一個類只能有一個實例(instance)
    • 可以 make it to private
  2. 它需要 "自行創建" 。(約束使用者
    • 使用該類的靜態變量來保存實例
  3. 自行向整個系統提供這個實例
    • 對外提供獲取的方法
      1. 可以公開靜態變量
      2. 也可以用靜態變量的 get 方法來獲取

# 幾種常見形式

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

  • 直接實例化餓漢式(簡潔直觀)
  • 枚舉式(最簡潔)
  • 靜態代碼塊餓漢式(適合複雜實例化

# 直接實例化餓漢式

  • 不管你是不需要這個對象都會創建
/*
	1. constructor 設成 private
	2. 自行創建,並且用靜態變量保存
	3. 向外提供這個實例
	4. 強調這是一個單例時,可以用 final 這個 keyword
*/
public class Singleton1 {
	public static final Singleton INSTANCE = new Singleton();
	private Singleton(){
	}
}

直接訪問:

public class TestSingleton1 {
	public stastic void main(String[] args) {
		// 因為它是 static final,所以直接取了
		Singleton1 s = Singleton1.INSTANCE;	
		//output: 該 INSTANCE 的 hashcode
	}
}

# 枚舉式

/*
	1. 枚舉類型,表示該類型的對象是有限的幾個
	2. 我們可以限定為 1 個,那就成了單例了
	
*/
public enum Singleton2 {
	INSTANCE  
}

訪問:

public class TestSingleton2 {
	public stastic void main(String[] args) {
		// 和剛剛是一樣的
		Singleton1 s = Singleton2.INSTANCE;	
		System.out.print(s);
		// 輸出的是:INSTANCE
		// 直接是這個常量的名字
	}
}

# 靜態代碼塊餓漢式

如果 constructor 接收的不是常量,是一個在某文件中的變量,那麼就會用到這個模式。

public class Singleton3 {
	public static final Singleton3 INSTANCE;
	private String info; // 這個 info 的值是存在某文件裡的
	
	static{
		try {
			Properties pro = new Properties();
		// 文件在 src 目錄下才能使用類加載器	pro.load (Singleton3.class.getClassLoader ().getResourceAsStream ("文件名"))
			INSTANCE = new Singleton3(pro.getProperty("info"));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		}
		
	private Singleton3(String info){
		this.info = info;
	}

# 懶漢式:延遲創建對象

  • 線程不安全(適用於單線程
  • 線程安全(適用於多線程
  • 靜態內部類形式(適用於多線程

# 線程不安全

/*  延遲創建對象
	1. constructor 設成 private
	2. 用一個靜態變量保存這個唯一的實例
	3. 提供一個靜態方法來獲取這個實例對象
*/
public class Singleton4 {
	// 設成 private
	private static final Singleton4 instance;
	private Singleton4(){
	}
	public static Singleton4 getInstance() {
		// 你沒創建過的話,就幫你建一個
		if (instance == null) {
		instance = new Singleton4();
		}
		// 有的話就直接返回實例
		return instance;
	}
}

訪問:
(這裡我沒有用寫多線程的代碼來演示)

public class TestSingleton4 {
	public stastic void main(String[] args) {
		Singleton4 s1 = Singleton4.getInstance();	
		// 可能訪問 instance 的它在阻塞,還沒創建,所以又會再創建一個
		// 最後導致 new 了這個 instance
		// 返回的 hashcode 指向的都是不用的 instance
		System.out.print(s1);
		System.out.print(s2);
		
	}
}

# 線程安全時

/*  
加上鎖
*/
public class Singleton5 {
	// 設成 private
	private static final Singleton5 instance;
	private Singleton5(){
	}
	public static Singleton5 getInstance() {
		synchronized (Singleton5.class) {
			if (instance == null) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				instance = new Singleton5();
		}
		return instance;
		}
	
	}
}

用同步方法

public class Singleton5 {
	// 設成 private
	private static final Singleton5 instance;
	private Singleton5(){
	}
	public static synchronized Singleton5 getInstance() {
	
			if (instance == null) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				instance = new Singleton5();
		}
		return instance;
	}
}

訪問:

public class Test {
	public static void main (String[] args) throws InterruptedException, ExecutionException {
	Callable<Singleton5> c = new Callable<Singleton5>() {
	@Override
	public Singleton5 call() throws Exception {
		return Singleton5.getInstance();
		}
	};
	ExecutorService es = Executors.newFixedThreadPool(2);
	Future<Singleton5> f1 = es.submit(c);
	Future<Singleton5> f2 = es.submit(c);
	Singleton5 s1 = f1.get();
	Singleton5 s2 = f2.get();
	
	System.out.println(s1 == s2);
	System.out.println(s1);
	System.out.println(s2);
	es.shutdown();
	
	}
}

# 再優化

public class Singleton5 {
	private static final Singleton5 instance;
	private Singleton5(){
	}
	public static Singleton5 getInstance() {
		if (instance == null) { // 之後再後來的線程不需要再等同步,因為已經 instance 不是等於 null
		// 可以直接返回 instance 了
			synchronized (Singleton5.class) {
			if (instance == null) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				instance = new Singleton5();
		}
		}
		return instance;
		}
	
	}
}

# volatile

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

# 靜態內部類

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