Singleton pattern and broken double checked locking in a real-world Java application
I was reading the article Double-checked locking and the Singleton pattern, on how double checked locking is broken, and some related questions here on Stack Overflow.
I have used this pattern/idiom several times without any issues. Since I have been using Java 5, my first thought was that this has been rectified in Java 5 memory model. However the article says:
This article refers to the Java Memory Model before it was revised fo开发者_运维问答r Java 5.0; statements about memory ordering may no longer be correct. However, the double-checked locking idiom is still broken under the new memory model.
Is this a real problem, and, if so, under what conditions?
Illustrating example of a singleton with respect to double-checked locking that looks clever but is broken
The start of a synchronization block guarantees that you see the latest data, but it does not guarantee reordering, you cannot expect a consistent view of data unless you are also in a synchronized block. It doesn't guarantee, that variables modifications done within synchronized section will be visible to other threads. Only the threads that enters the synchronized block is guaranteed to see the changes. This is the reason why double checked locking is broken - it is not synchronized on the reader's side. The reading thread may see, that the singleton is not null, but singleton data may not be fully initialized (visible).
On the other hand, ordering is provided by volatile which guarantees ordering, for instance write to volatile singleton static field guarantees that writes to the singleton object will be finished before the write to a volatile static field. It doesn't prevent creation singleton of two objects; this is provided by synchronize. Class final static fields doesn't need to be volatile. In Java, the JVM takes care of this problem.
More can be found in:
- Double-checked locking: Clever, but broken
- The "Double-Checked Locking is Broken" Declaration
It would be difficult for someone to be sure that their application had actually been hit by a double-checked lock failure. Indeed, many applications that used this idiom may never experience the problem for a variety of reasons.
However, that doesn't mean that you should use it. The mere fact there is a non-quantifiable probability of failure should be sufficient to persuade you not to use double-checked locking, especially since there are safe alternatives.
You've just been lucky.
We had an application which used a broken double-check idiom, and it functioned perfectly for a very long time - no, in fact, I have never experienced problems with this idiom. Of course I fixed it regardless of that.
I guess one of the reasons of this is that thread visibility will eventually attained in the real world. And once attained, it stays. So yes, it will be very difficult to detect whether the problem has happened.
I believe the hashCode()
implementation of String
partially relies on this fact... Threads calculate hashCode while they don't see the cache, but eventually they start to see. Meanwhile, duplicate calculation means just some wasted CPU time, and the benefit of avoiding the memory effect of volatile semantics trumps this wasted effort (at least that's why they implemented it that way I guess). The idiom that has valid use is (actual String.hashCode() implementation):
/** Cache the hash code for the string */
private int hash; // Defaults to 0
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
Obviously, one has to think and measure a lot before using it.
精彩评论