问题
I came across the following code in an article somewhere on the Internet:
public class MyInt {
private int x;
public MyInt(int y) {
this.x = y;
}
public int getValue() {
return this.x;
}
}
The article states that
Constructors are not treated special by the compiler (JIT, CPU etc) so it is allowed to reorder instructions from the constructor and instructions that come after the constructor.
Also, this JSR-133 article about the Java Memory Model states that
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.
The abovementioned MyInt
instance seems immutable (except that the class is not marked final
) and thread-safe, but the articles state it is not. They state that it's not guaranteed that x
always has the correct value upon read.
But I thought that
only the thread that creates an object should have access to it while it is being constructed
and the Java Tutorials seem so support that.
My question is: does it mean that, with the current JMM, a thread can have access to a partially constructed object due to instruction reordering? And if yes, how? And does that mean that the statement from the Java Tutorials is simply not true?
回答1:
That article is saying that if you have code like
foo = new MyInt(7);
in a class that has a field
MyInt foo;
then the instructions that amount to
(reference to new object).x = 7;
foo = (reference to new object);
could be swapped over as some kind of optimisation. This will never change the behaviour of the thread that's running this code, but it's possible that some other thread will be able to read foo
after the line
foo = (reference to new object);
but before the line
(reference to new object).x = 7;
in which case it would see foo.x
as 0
, not 7
. That is to say, that other thread could run
int bar = someObject.getFoo().getValue();
and end up with bar
equal to 0
.
I've never seen anything like this happen in the wild, but the author seems to know what he's talking about.
回答2:
Instruction reordering alone can not lead to another thread seeing a partially constructed object. By definition, the JVM is only allowed to reorder things if they don't affect a correctly synchronized program's behaviour.
It's unsafe publishing of the object reference that enables bad things to happen. Here's a particularly poor attempt at a singleton for example:
public class BadSingleton {
public static BadSingleton theInstance;
private int foo;
public BadSingleton() {
this.foo = 42;
if (theInstance == null) {
theInstance = this;
}
}
}
Here you accidentally publish the reference to the object being constructed in a static
field. This would not necessarily be a problem until the JVM decides to reorder things and places this.foo = 42
after the assignment to theInstance
. So the two things together conspire to break your invariants and allow another thread to see a BadSingleton.theInstance
with its foo
field uninitialised.
Another frequent source of accidental publication is calling overrideable methods from the constructor. This does not always lead to accidental publication, but the potential is there, hence it should be avoided.
only the thread that creates an object should have access to it while it is being constructed
And does that mean that the statement from the Java Tutorials is simply not true?
Yes and no. It depends on how we interpret the word should
. There is no guarantee that in every possible case another thread won't see a partially constructed object. But it's true in the sense that you should write code that doesn't allow it to happen.
来源:https://stackoverflow.com/questions/45857765/partial-constructed-objects-in-the-java-memory-model