做爲一個電氣工程師,研究等離子體方向,最近在自學設計模式,此爲整理博客。
設計模式能夠分爲三大類,分別是建立型設計模式、行爲型設計模式以及結構型設計模式。 java
單例模式是建立型的設計模式的一種。設計模式
心法:安全
構造器私有app
私有單例對象less
公有靜態方法獲取單例對象ide
public class Singleton{ private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
特色:線程安全,可是沒有實現lazy load.所謂的lazy load,根據Bob Lee的說法,性能
In production, you typically want to eagerly load all your singletons so you catch errors early and take any performance hit up front, but in tests and during development, you only want to load what you absolutely need so as not to waste time.優化
那麼如何實現lazy load呢?ui
public class Singleton{ private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
懶漢式的確實現了lazy load的問題,可是它也帶來了新的問題,線程安全沒法保證。咱們能夠在getInstance()方法上加synchronized,固然也能夠給instance加volatile,這一樣是Bob Lee的見解,他說this
But volatile isn't that much faster than synchronized, synchronized is pretty fast nowadays.
可是與此同時,我還看到了更深刻的討論,如
http://stackoverflow.com/a/46... So the overall cost of a volatile read will roughly equivalent of a memory load and can be as cheap as a L1 cache access. However if another core is writing to the volatile variable, the cache-line will be invalidated requiring a main memory or perhaps an L3 cache access. The actual cost will depend heavily on the CPU architecture.
這就太深刻了,暫時不考慮,可是下面的建議仍是不錯的:
Nevertheless you shouldn't make a variable volatile unless you know that it will be accessed from multiple threads outside of synchronized blocks. Even then you should consider whether volatile is the best choice versus synchronized, AtomicReference and its friends, the explicit Lock classes, etc.
所以我並未在以下的代碼中添加volatile關鍵字
public class Singleton{ private static Singleton instance; private Singleton(){ } public synchronized static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
雙鎖機制Double-Checked Locking(DCL)能夠對性能進行進一步的優化
public class Singleton{ private static Singleton instance; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
雙鎖機制的問題:
So what's broken about DCL?
DCL relies on an unsynchronized use of the resource field. That appears to be harmless, but it is not. To see why, imagine that thread A is inside the synchronized block, executing the statement resource = new Resource(); while thread B is just entering getResource(). Consider the effect on memory of this initialization. Memory for the new Resource object will be allocated; the constructor for Resource will be called, initializing the member fields of the new object; and the field resource of SomeClass will be assigned a reference to the newly created object.
However, since thread B is not executing inside a synchronized block, it may see these memory operations in a different order than the one thread A executes. It could be the case that B sees these events in the following order (and the compiler is also free to reorder the instructions like this): allocate memory, assign reference to resource, call constructor. Suppose thread B comes along after the memory has been allocated and the resource field is set, but before the constructor is called. It sees that resource is not null, skips the synchronized block, and returns a reference to a partially constructed Resource! Needless to say, the result is neither expected nor desired.
When presented with this example, many people are skeptical at first. Many highly intelligent programmers have tried to fix DCL so that it does work, but none of these supposedly fixed versions work either. It should be noted that DCL might, in fact, work on some versions of some JVMs -- as few JVMs actually implement the JMM properly. However, you don't want the correctness of your programs to rely on implementation details -- especially errors -- specific to the particular version of the particular JVM you use.
Other concurrency hazards are embedded in DCL -- and in any unsynchronized reference to memory written by another thread, even harmless-looking reads. Suppose thread A has completed initializing the Resource and exits the synchronized block as thread B enters getResource(). Now the Resource is fully initialized, and thread A flushes its local memory out to main memory. The resource's fields may reference other objects stored in memory through its member fields, which will also be flushed out. While thread B may see a valid reference to the newly created Resource, because it didn't perform a read barrier, it could still see stale values of resource's member fields.
簡單說來,DCL致使的問題是,初始化實例的寫入操做和實例字段的寫入操做可以被編譯器或者緩衝區重排序,重排序可能會致使返回部分構造的一些東西。就是咱們讀取到了一個沒有初始化的對象。
對此,有更好的解決方法,即保證了線程安全,又實現了lasy load——Initialization on Demand Holder即靜態內部類實現
public class Singleton{ private Singleton(){ } private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
以上方法都有可能經過反射或序列化與反序列化來破解,具體內容參見 反射與(反)序列化問題,而枚舉類則沒有如上問題
public enum Singleton{ INSTANCE; }
public class Reflect{ public static void main(String[] args) throws Exception{ Class<Singleton> clazz = (Class<Singleton>)Class.forName("bin.pattern.Singleton"); Constructor c = clazz.getDeclaredConstructor(null); c.setAccessible(true); Singleton s1 = Singleton.getInstance(); Singleton s2 = (Singleton)c.newInstance(); System.out.println(s1 == s2); } }
返回false,說明產生了新的對象。修復方法:Singleton.java
public class Singleton{ private static Singleton instance; private Singleton(){ if(instance != null){ throw new RuntimeException(); } } }
public class Serialization{ public static void main(String[] args) throws IOException,ClassNotFoundException{ Singleton s1 = Singleton.getInstance(); FileOutputStream fos = new FileOutputStream("/Users/bin/Documents/workspace/myjdk/a.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/bin/Documents/workspace/myjdk/a.txt")); Singleton s2 = (Singleton)ois.readObject(); System.out.println(s1 == s2); } }
輸出false,說明又跪了。解決方法:Singleton.java
public class Singleton implements Serializable{ //添加方法 private Object readResolve() { return SingletonHolder.INSTANCE; } }