想整理一些 java 併發相關的知識,不知道從哪開始,想起了單例模式中要考慮的線程安全,就從單例模式開始吧。
之前寫過單例模式,這裏再從新彙總補充整理一下,單例模式的多種實現。html
單例模式那件小事,看了你不會後悔
單例模式不是一件小事,快回來看看java
以前在第一篇文章說,單例模式的主要思想是:安全
這種說法看上去沒錯,但也好像不太準確。其實,就算外界能隨意 new 出新的實例對象,但只要咱們保證咱們每次使用的對象是惟一的,就能夠。併發
public class Singleton { private Singleton() { } private static Singleton sSingleton = new Singleton(); public static Singleton getInstance() { return sSingleton; } }
public class Singleton { private Singleton() { } public static final Singleton sSingleton = new Singleton(); }
將實例對象用 public static final
修飾,不提供公開方法獲取實例,直接經過 Singleton.sSingleton
獲取。函數
public class Singleton { private Singleton() { } private static Singleton sSingleton; public static Singleton getInstance() { if (sSingleton == null) { sSingleton = new Singleton(); } return sSingleton; } }
public class Singleton { private Singleton() { } private static Singleton sSingleton; public synchronized static Singleton getInstance() { if (sSingleton == null) { sSingleton = new Singleton(); } return sSingleton; } }
getInstance
都進行同步,形成沒必要要的同步開銷,這種模式通常不建議使用。public class Singleton { private Singleton() { } /** * volatile is since JDK5 */ private static volatile Singleton sSingleton; public static Singleton getInstance() { if (sSingleton == null) { synchronized (Singleton.class) { // 未初始化,則初始instance變量 if (sSingleton == null) { sSingleton = new Singleton(); } } } return sSingleton; } }
sSingleton = new Singleton() 不是一個原子操做。(XXX)故須加 volatile
關鍵字修飾,該關鍵字在 jdk1.5 以後版本纔有。高併發
public class Singleton { private Singleton () { } private static class InnerClassSingleton { private final static Singleton sSingleton = new Singleton(); } public static Singleton getInstance() { return InnerClassSingleton.sSingleton; } }
優勢:推薦使用。測試
public enum Singleton{ INSTANCE; // 其它方法 public void doSomething(){ ... } }
import java.util.HashMap; import java.util.Map; public class Singleton { private static Map<String, Object> objMap = new HashMap<String, Object>(); private Singleton() { } public static void registerService(String key, Object instance) { if (!objMap.containsKey(key)) { objMap.put(key, instance); } } public static Object getService(String key) { return objMap.get(key); } }
利用了 HashMap 容器 key 不可重複的特性。線程
前面的多種實現方法中,不少咱們按照構造方法私有化的思想來實現的,咱們知道,利用反射,仍然能夠建立出新對象,這樣在反射場景中,這種思想實現的單例模式就失效了,那麼如何防止反射破壞單例模式呢?原理上就是在存在一個實例的狀況下,再次調用構造方法時,拋出異常。下面以靜態內部類的單例模式爲例:code
public class Singleton { private static boolean flag = false; private Singleton(){ synchronized(Singleton.class) { if(flag == false) { flag = !flag; } else { throw new RuntimeException("單例模式被侵犯!"); } } } private static class InnerClassSingleton { private final static Singleton sSingleton = new Singleton(); } public static Singleton getInstance() { return InnerClassSingleton.sSingleton; } }
具體測試代碼,見 單例模式不是一件小事,快回來看看htm
經過序列化能夠講一個對象實例寫入到磁盤中,經過反序列化再讀取回來的時候,即使構造方法是私有的,也依然能夠經過特殊的途徑,建立出一個新的實例,至關於調用了該類的構造函數。要避免這個問題,咱們須要在代碼中加入以下方法,讓其在反序列化過程當中執行 readResolve 方法時返回 sSingleton 對象。
private Object readResolve() throws ObjectStreamException { return sSingleton; }
有沒有一種方式實現的單例模式在任何狀況下都是一個單例呢?
—— 有。就是上面說的枚舉單例。枚舉,就能保證在任何狀況下都是單例的,而且是線程安全的。