问题
public class Factory {
private Singleton instance;
public Singleton getInstance() {
Singleton res = instance;
if (res == null) {
synchronized (this) {
res = instance;
if (res == null) {
res = new Singleton();
instance = res;
}
}
}
return res;
}
}
It is almost correct implementation of thread-safe Singleton
. The only problem I see is:
The thread #1
that is initializing the instance
field can published before it will be initialized completely. Now, the second thread can read instance
in a inconsistent state.
But, for my eye it is only problem here. Is it only problem here?
(And we can make instance
volatile).
回答1:
You example is explained by Shipilev in Safe Publication and Safe Initialization in Java. I highly recommend to read the whole article, but to sum it up look at UnsafeLocalDCLFactory
section there:
public class UnsafeLocalDCLFactory implements Factory {
private Singleton instance; // deliberately non-volatile
@Override
public Singleton getInstance() {
Singleton res = instance;
if (res == null) {
synchronized (this) {
res = instance;
if (res == null) {
res = new Singleton();
instance = res;
}
}
}
return res;
}
}
Above has following problems:
The introduction of local variable here is a correctness fix, but only partial: there still no happens-before between publishing the Singleton instance, and reading of any of its fields. We are only protecting ourselves from returning "null" instead of Singleton instance. The same trick can also be regarded as a performance optimization for SafeDCLFactory, i.e. doing only a single volatile read, yielding:
Shipilev suggests to fix as follows, by marking instance
volatile:
public class SafeLocalDCLFactory implements Factory {
private volatile Singleton instance;
@Override
public Singleton getInstance() {
Singleton res = instance;
if (res == null) {
synchronized (this) {
res = instance;
if (res == null) {
res = new Singleton();
instance = res;
}
}
}
return res;
}
}
There are no other problems with this example.
回答2:
Normally I would never use a double checked locking mechanism anymore. To create a thread safe singleton you should let the compiler do this:
public class Factory {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return res;
}
}
Now you are talking to make the instance volatile. I don't think this is necessary with this solution as the jit compiler now handlers the synchronization of the threads when the object is constructed. But if you want to make it volatile, you can.
Finally I would make the getInstance() and the instance static. Then you can reference Factory.getInstance() directly without constructing the Factory class. Also: you will get the same instance across all threads in your application. Else every new Factory() will give you a new instance.
You can also look at Wikipedia. They have a clean solution if you need a lazy solution:
https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
// Correct lazy initialization in Java
class Foo {
private static class HelperHolder {
public static final Helper helper = new Helper();
}
public static Helper getHelper() {
return HelperHolder.helper;
}
}
回答3:
EDIT I've written one more answer here that should clear all the confusion.
This is a good question, and I'll try to summarize my understanding here.
Suppose Thread1
is currently initializing Singleton
instance and publishes the reference (unsafely obviously). Thread2
can see this un-safe published reference (meaning it sees a non-null reference), but that does not mean that the fields that it sees via that reference (Singleton
fields that are initialized via the constructor) are initialized correctly too.
As far as I can see, this happens because there could be re-ordering of the stores of the fields happening inside the constructor. Since there is no "happens-before" rules (these are plain variables), this could be entirely possible.
But that is not the only problem here. Notice that you do two reads here:
if (res == null) { // read 1
return res // read 2
These reads have no synchronization protection, thus these are racy reads. AFAIK this means that read 1 is allowed to read a non-null reference, while read 2 is allowed to read a null reference.
This btw is the same thing that the ALL mighty Shipilev explains (even if I read this article once 1/2 year I still find something new every time).
Indeed making instance volatile
would fix things. When you make it volatile, this happens:
instance = res; // volatile write, thus [LoadStore][StoreStore] barriers
All "other" actions (stores from within the constructor) can not pass this fence, there will be no re-orderings. It also means that when you read the volatile variable and see a non-null value, it means that every "write" that was done before writing the volatile itself has occurred for sure. This excellent post has the exact meaning of it
This also solves the second problem, since these operations can not be re-ordered, you are guaranteed to see the same value from read 1
and read 2
.
No matter how much I read and try to understand these things are constantly complicated to me, there are very few people that I know that can write code like this and reason correctly about it too. When you can (I do!) please stick to the known and working examples of double check locking :)
回答4:
I do that like this:
public class Factory {
private static Factory factor;
public static Factory getInstance() {
return factor==null ? factor = new Factory() : factor;
}
}
Just simply
回答5:
After some time (yeah it took 2 years, I know), I think I have the proper answer. To take it literally, the answer to this:
But, for my eye it is only problem here. Is it only problem here?
Would be yes. The way you have it right now, callers of getInstance
will never see a null
. But if Singleton
would have fields, there is no guarantee that those fields will be correctly initialized.
Let's take this slow, since the example is beautiful, IMHO. That code you showed does a single (racy) volatile read
:
public class Factory {
private Singleton instance;
public Singleton getInstance() {
Singleton res = instance; // <-- volatile RACY read
if (res == null) {
synchronized (this) {
res = instance; // <-- volatile read under a lock, thus NOT racy
if (res == null) {
res = new Singleton();
instance = res;
}
}
}
return res;
}
}
Usually, the classical "double check locking" has two racy reads of volatile, for example:
public class SafeDCLFactory {
private volatile Singleton instance;
public Singleton get() {
if (instance == null) { // <-- RACY read 1
synchronized(this) {
if (instance == null) { // <-- non-racy read
instance = new Singleton();
}
}
}
return instance; // <-- RACY read 2
}
}
Because those two reads are racy, without volatile, this pattern is broken. You can read how we can break here, for example.
In your case, there is an optimization, that does one less reading of a volatile field. On some platforms this matters, afaik.
The other part of the question is more interesting. What if Singleton
has some fields that we need to set?
static class Singleton {
//setter and getter also
private Object obj;
}
And a factory, where Singleton
is volatile
:
static class Factory {
private volatile Singleton instance;
public Singleton get(Object obj) {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = new Singleton();
instance.setObj(obj);
}
}
}
return instance;
}
}
We have a volatile field, we are safe, right? Wrong. The assign of obj
happens after the volatile write, as such there are no guarantees about it. In plain english: this should help you a lot.
The correct way to fix this is to do the volatile write with an already build instance (fully build):
if (instance == null) {
Singleton local = new Singleton();
local.setObj(obj);
instance = local;
}
回答6:
Now, the second thread can read instance in a inconsistent state.
I'm pretty sure that really is the only issue in that code. The way I understand it, as soon as the line
instance = res;
is executed, another thread could read instance
and see it as non-null, and thus skips the synchronized
. This means there is no happens-before relation between those two threads, because those only exist if both threads synchronize on the same object or access the same volatile fields.
The other answers already linked to Safe Publication and Safe Initialization in Java, which offers the following ways to solve the unsafe publication:
Making the
instance
fieldvolatile
. All threads have to read the samevolatile
variable, which establishes a happens-before relationA write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
Wrapping the singleton into a wrapper which stores the singleton in a
final
field. The rules forfinal
fields are not as formally specified as the happens-before relations, the best explanation I could find is in final Field SemanticsAn object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.
(Not the emphasis and restriction to the final fields, other fields might be seen in inconsistent state at least theoretically)
Making sure the singleton itself only contains final fields. The explanation would be the same as the one above.
回答7:
The problem with the code mentioned in the question is that reordering
can happen and a thread can get a partially constructed object of the singleton class.
When I say reordering
, I mean the following:
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
/* The above line executes the following steps:
1) memory allocation for Singleton class
2) constructor call ( it may have gone for some I/O like reading property file etc...
3) assignment ( look ahead shows it depends only on memory allocation which has already happened in 1st step.
If compiler changes the order, it might assign the memory allocated to the instance variable.
What may happen is half initialized object will be returned to a different thread )
*/
}
}
}
return instance;
}
Declaring the instance variable volatile
ensures a happens-before/ordered relationship on the above mentioned 3 steps:
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
From Wikipedia's Double-checked locking:
As of J2SE 5.0, this problem has been fixed. The volatile keyword now ensures that multiple threads handle the singleton instance correctly. This new idiom is described in The "Double-Checked Locking is Broken" Declaration:
// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
Helper result = helper;
if (result == null) {
synchronized(this) {
result = helper;
if (result == null) {
helper = result = new Helper();
}
}
}
return result;
}
// other functions and members...
}
来源:https://stackoverflow.com/questions/47638396/publishing-and-reading-of-non-volatile-field