Singleton Pattern :
限制類別只能產生一個實體並提供全域存取方法。
Singleton Pattern 大致上可以分為 2 種形式,飢餓型以及懶人型,而不論哪種形式都相當簡單。
飢餓型,在第3行宣告 sUniqueInstance 變數立即產生實體。
public class HungerSingleton { private static HungerSingleton sUniqueInstance = new HungerSingleton(); private HungerSingleton() { } public static HungerSingleton getInstance() { return sUniqueInstance; } }
懶人型,在第3行宣告 sUniqueInstance 變數並不馬上建立實體,而是等到需要實體才建立(第12行)。
public class LazySingleton { private static LazySingleton sUniqueInstance; private LazySingleton(){ } public static LazySingleton getInstance() { if (sUniqueInstance == null) { sUniqueInstance = new LazySingleton(); } return sUniqueInstance; } }
使用 Singleton Pattern 需要注意的是多執行緒的問題。
因為使用靜態初始化的關係,HungerSingleton 可以確保只產生一個 instance。
LazySingleton 由於使用判斷 null 值來產生實體,當 2 條執行緒同時執行到第11,12行,就有可能產生 2 個 Singleton ,就不是單例了。
最簡單的解決方式可以加上 synchronized 來限制同時只能有一條執行緒進入 getInstance 方法 如下
public static synchronized LazySingleton getInstance() { if (sUniqueInstance == null) { sUniqueInstance = new LazySingleton(); } return sUniqueInstance; }
另一種比較麻煩的解決方法為DCL(double checked locking), 但這種解法有個限制就是只能支援 jvm 1.5之後的版本。
public class DCLSingleton { private static volatile DCLSingleton sUniqueInstance; private static Object mLock = new Object(); public static DCLSingleton getInstance() { if (sUniqueInstance == null) { synchronized (mLock) { if (sUniqueInstance == null) { sUniqueInstance = new DCLSingleton(); } } } return sUniqueInstance; } }
如果情況予許還是選擇 HungerSingleton 來避免可能產生多個實體的情況,但 HungerSingleton 有另外一個缺點。
因為沒有實現 lazy load , 若其本身佔用大量記憶體,會在程序開始的時候造成浪費記憶體的情況。
可以使用靜態內部類別(static inner class)來實現 lazy load + HungerSingleton
public class InnerStaticHungerSingleton { private InnerStaticHungerSingleton() { //Prevents utility class being instantiated } private static class InnerInstance{ private static final InnerStaticHungerSingleton UNIQUE_INSTANCE = new InnerStaticHungerSingleton(); } public static InnerStaticHungerSingleton getInstance() { return InnerInstance.UNIQUE_INSTANCE; } }
另外 LazySingleton 實現了 lazy load 作法。也就是為了節省記憶體使用空間,只有在變數需要實體才產生。
除了 lazy load 可以節省記憶體之外,還有另一個技術 cache 可以達到相同的效果。
simple cache in java
public class SimpleCache { private Map<String, Object> mCache = new HashMap<String, Object>(); private static final String KEY_OF_DATA = "keyOfData"; public Object getCache() { Object data = mCache.get(KEY_OF_DATA); if (data == null) { data = new Object(); mCache.put(KEY_OF_DATA, data); } return data; } }
簡單的 cache 實作,主要使用 Map 來達成,第一次呼叫 getCache 會進入第10行,並把 data 存入 map 中,隨後需要使用的時候再從 map 取出即可。
不需要重複產生實體,其實再複雜一點的變化型就是 Flyweight Pattern(享元模式),其作用也是解決需要大量細微性物件時,記憶體不夠的狀態。