關於double-checked locking的問題,必讀這篇文章《雙重檢查鎖定與延遲初始化》html
The (infamous) double-checked locking idiom (also called the multithreaded singleton pattern) is a trick designed to support lazy initialization while avoiding the overhead of synchronization. In very early JVMs, synchronization was slow, and developers were eager to remove it -- perhaps too eager. The double-checked locking idiom looks like this:安全
Double-checked locking,也叫多線程單例模式,是用來在延遲初始化對象的時候,避免同步的開銷,由於在早期的JVM中,同步是很慢的。多線程
// double-checked-locking - don't do this! private static Something instance = null; public Something getInstance() { if (instance == null) { synchronized (this) { if (instance == null) //此處,初始化和賦值引用可能重排序 instance = new Something(); } } return instance; }
This looks awfully clever -- the synchronization is avoided on the common code path. There's only one problem with it -- it doesn't work. Why not? The most obvious reason is that the writes which initialize instance and the write to the instance field can be reordered by the compiler or the cache, which would have the effect of returning what appears to be a partially constructed Something. The result would be that we read an uninitialized object. There are lots of other reasons why this is wrong, and why algorithmic corrections to it are wrong. There is no way to fix it using the old Java memory model. More in-depth information can be found at Double-checked locking: Clever, but broken and The "Double Checked Locking is broken" declaration併發
以上是一個double-checked locking,儘管看起來很完美,使用普通代碼就避免掉了同步,但其實並不起做用。app
緣由是,instance = new Something();至關於如下三步ide
其中,2和3可能重排序。詳見文獻 Double-checked locking: Clever, but broken 和 The "Double Checked Locking is broken" declaration。測試
Many people assumed that the use of the volatile keyword would eliminate the problems that arise when trying to use the double-checked-locking pattern. In JVMs prior to 1.5, volatile would not ensure that it worked (your mileage may vary). Under the new memory model, making the instance field volatile will "fix" the problems with double-checked locking, because then there will be a happens-before relationship between the initialization of the Something by the constructing thread and the return of its value by the thread that reads it.this
一些人用volatile來解決使用double-checked-locking單例模式帶來的問題,但在1.5以前,volatile也未必有用。但在新內存模型中,給instance加個volatile修飾符則能夠解決上述問題,由於JMM設置了一個happen-before關係:構造線程對instance的初始化 happen before 其它線程讀取這個對象。線程
事實上,volatile是經過禁止上述2和3重排序實現的。debug
Instead, use the Initialization On Demand Holder idiom, which is thread-safe and a lot easier to understand:
相反的,使用Demand Holder來線程安全的完成初始化,會容易好多,Demand Holder以下
private static class LazySomethingHolder { public static Something something = new Something(); } public static Something getInstance() { return LazySomethingHolder.something; }
This code is guaranteed to be correct because of the initialization guarantees for static fields; if a field is set in a static initializer, it is guaranteed to be made visible, correctly, to any thread that accesses that class.
用以上方法來實現單例是正確的,由於類在初始化是靜態字段時,對任何線程都是可見的。
You should look at http://gee.cs.oswego.edu/dl/jmm/cookbook.html .
Why should you care? Concurrency bugs are very difficult to debug. They often don't appear in testing, waiting instead until your program is run under heavy load, and are hard to reproduce and trap. You are much better off spending the extra effort ahead of time to ensure that your program is properly synchronized; while this is not easy, it's a lot easier than trying to debug a badly synchronized application.
我爲何要care?併發bug是很難改的,通常測試不出來,只有在程序負載很大的時候纔會出現,並且很難復現。花大精力作好同步,總比改bug好啊~