Reputation: 3592
1. class Foo {
2. private Helper helper = null;
3. public Helper getHelper() {
4. if (helper == null) {
5. synchronized(this) {
6. if (helper == null) {
7. helper = new Helper();
8. }
9. }
10. }
11. return helper;
12. }
13. }
The reason why this structure is considered broken is generally described the reordering of assignments done by the compiler such that the Helper constructor is called after the write to the helper variable. My question is that how is this code thread-safe and are the following steps possible?
I don't see how this solution is any better than single checked locking.
Upvotes: 1
Views: 104
Reputation: 40256
It's still broken because the writes of any fields within Helper
may not be published even though the Helper
instance is not-null. For instance:
class Helper {
int someField;
public Helper(){
someField = 10;
}
}
In this case, it is possible according to the JLS to have something like:
Helper someHelper = getHelper();
if(someHelper.someField == 0){
// error
}
The line marked //error
in theory could be hit since the read & write of someField aren't synchronized with the write of helper
within getHelper()
.
Upvotes: 0
Reputation: 20059
This works for the reference to the helper, but is still subtly broken.
Its broken because the VM is allowed to reorder program actions within the synchronized block as much as it likes, so the reference helper can be set to non-null before construction of the helper instance (by Thread 1) has completed.
Thread 2 can now see not-null outside the synchronized block, never attempts to enter the synchronized block (Thread 1 would be still holding the lock and be busy constructing Helper) and works with the half-constructed Helper instance.
This may or may not happen on a specific VM version. But the specification explicitly allows a VM to do this. Thats why the example is broken. It could be fixed by declaring helper volatile (only with Java 5+).
Upvotes: 4
Reputation: 73558
How could thread 1 give up the monitor after checking that helper is null? It won't release the lock until it has initialized helper.
This didn't work years ago on the JVM, but they changed the memory model and it fixed this.
The current "best" way is not DCL, but to implement singletons as enums.
Upvotes: 2