單例模式的定義:
Singleton pattern restricts the instantiation of a class and ensures that only one instance of the class exists in the java virtual machine. The singleton class must provide a global access point to get the instance of the class.
限制一個類的執行個體在一個jvm執行個體中確保只有一個,而且必須提供一個全域訪問點獲得該單例。
為什麼會出現單例模式:
• 減少記憶體開支。由於單例在記憶體中相對於一個jvm執行個體內只有一個執行個體對象,不會重複的建立和jvm記憶體回收,對於記憶體減少了空間佔用,也利於jvm記憶體回收處理。
• 減少系統效能開銷。當一個對象的產生依賴較多的資源時,比如讀取配置或者依賴其他對象的時候,單例在jvm啟動的時候預先載入資源,然後可以永久駐留記憶體,當然也減少了jvm的記憶體回收線程的負擔。
•當然還有很有的優勢,現流行的spring架構就是預設支援單例模式(相對應spring容器)。
單例的實現方式:
實現單例模式的方式有很多不同手段,但以下幾點我們會同時考慮:
•建構函式必須私人,不能讓別人有許可權隨意執行個體化
•該單例一般在類中有一個私人靜態變數
•該單例一般提供一個靜態公用方法獲得該單例(對於外界的該單例的全域訪問點)
餓漢式(Eager initialization)
餓漢式單例實現方式是在類載入的時候初始化該單例。這種方式實現單例最簡單,但也有個缺點就是即使我們應用中沒有使用該類的單例,但類載入的時候也必須初始化。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年7月31日 下午11:36:05
*
* 餓漢式 單例
* EagerInitialized Singleton
*/
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
private EagerInitializedSingleton() {
}
public static EagerInitializedSingleton getInstance() {
return instance;
}
public void doSomething() {
System.out.println("test");
}
public static void main(String[] args) {
System.out.println(EagerInitializedSingleton.getInstance());
System.out.println(EagerInitializedSingleton.getInstance());
EagerInitializedSingleton.getInstance().doSomething();
// com.doctor.design_pattern.singleton.EagerInitializedSingleton@2a139a55
// com.doctor.design_pattern.singleton.EagerInitializedSingleton@2a139a55
// test
System.out.println(EagerInitializedSingleton.getInstance() == EagerInitializedSingleton.getInstance());
// true
}
}
當單例沒有涉及到過多的資源使用的時候,餓漢式單例比較適合。但很多情境下,單例的使用一般是為了使用一些資源比如檔案系統、資料庫連接等等,這中情境下,一般我們盡量使得該單例必須使用的時候,才會初始化,以避免資源的浪費使用。而且餓漢式單例也沒提供異常的處理機制。
Static block initialization
靜態塊初始化單例和餓漢式單例相似,差別在於執行個體的初始化在靜態塊中,這中方式提供了異常處理。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年8月1日 上午12:30:08
*/
public class StaticBlockSingleton {
private static StaticBlockSingleton instance;
public static StaticBlockSingleton getInstance() {
return instance;
}
static {
try {
instance = new StaticBlockSingleton();
} catch (Exception e) {
throw new RuntimeException("Exception occured in creating singleton instance");
}
}
public static void main(String[] args) {
System.out.println(StaticBlockSingleton.getInstance());
System.out.println(StaticBlockSingleton.getInstance());
System.out.println(StaticBlockSingleton.getInstance() == StaticBlockSingleton.getInstance());
// com.doctor.design_pattern.singleton.StaticBlockSingleton@2a139a55
// com.doctor.design_pattern.singleton.StaticBlockSingleton@2a139a55
// true
}
}
Lazy Initialization
懶初始化方法承擔了單例的建立,並且是擷取該單例的進入點。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年8月1日 上午12:39:40
*/
public class LazyInitializedSingleton {
private static LazyInitializedSingleton instance;
public static LazyInitializedSingleton getInstance() {
if (instance == null) {
instance = new LazyInitializedSingleton();
}
return instance;
}
private LazyInitializedSingleton() {
}
public static void main(String[] args) {
System.out.println(LazyInitializedSingleton.getInstance() == LazyInitializedSingleton.getInstance());// true
}
}
上面幾種單例的實現在單線程環境下可以很好的工作,但可能面臨多安全執行緒問題。
Thread Safe Singleton
安全執行緒單例,簡單的安全執行緒我們可以用
synchronized實現。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年8月1日 上午11:19:36
*
* Thread Safe Singleton
*
*/
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
private ThreadSafeSingleton() {
}
public static void main(String[] args) {
System.out.println(ThreadSafeSingleton.getInstance() == ThreadSafeSingleton.getInstance());
// true
}
}
上面的安全執行緒鎖的粒度是比較大了,鎖的粒度越大,並發性約不能,導致效能下降,我們可以用double checked locking 規則減少鎖的粒度。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Created on 2016年8月1日 上午11:37:25
*
* double checked locking principle
*/
public class DoubleCheckedLockingThreadSafeSingleton {
private static DoubleCheckedLockingThreadSafeSingleton instance;
public static DoubleCheckedLockingThreadSafeSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLockingThreadSafeSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedLockingThreadSafeSingleton();
}
}
}
return instance;
}
private DoubleCheckedLockingThreadSafeSingleton() {
}
public static void main(String[] args) {
}
}
Inner static helper class Singleton
採用內部類來實現資源的懶載入:
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Inner static helper class Singleton
*
*
* Created on 2016年8月1日 上午11:54:05
*/
public class InnerStaticHelperClassSingleton {
private InnerStaticHelperClassSingleton() {
}
public static InnerStaticHelperClassSingleton getInstance() {
return SingletonHelper.instance;
}
private static class SingletonHelper {
private static final InnerStaticHelperClassSingleton instance = new InnerStaticHelperClassSingleton();
}
public static void main(String[] args) {
}
}
內部類擁有外部類的執行個體,當外部類被jvm載入的時候,內部類沒有被載入,外部類的執行個體也就沒有被執行個體化,當外部類真正的調用得到單例入口方法的時候,才會觸發內部類的載入,同時外部類的單例也就初始化一次。
這種方式的好處就是不用鎖同步,就可以實現安全執行緒的單例。
枚舉單例實現Enum Singleton
上面的單例實現可能由於反射導致不能保證一個類只有一個執行個體。下面的枚舉實現方式克服了這個問題,但這種實現放手不能消極式載入。
package com.doctor.design_pattern.singleton;
/**
* @author sdcuike
*
* Enum Singleton
* 枚舉實現單例不能消極式載入資源,但保證了enum值只執行個體化一次。而且克服了反射帶來的問題
*
* Created on 2016年8月1日 下午12:24:06
*/
public enum EnumSingleton {
instance;
private EnumSingleton() {
}
public void doSomething() {
System.out.println("test do ");
}
public static void main(String[] args) {
EnumSingleton.instance.doSomething();
}
}
序列化與單例
我們一般通過網路互動的時候都會用到序列化,序列化打破了單例的規則,還原序列化我們得到了另一個執行個體(請自行驗證)。為了保證序列化不影響單例規則,我們一般實現以下方法:
protected Object readResolve() {
return getInstance();
}