问题
I read several related questions, but none of them explains ways of safe publication of the Holder. I am still confused about example from Java Concurrency in Practice, section 3.5:
There is the class Holder:
public Holder {
private int n;
public Holder(int n) { this.n = n };
public void assertSanity() {
if(n != n)
throw new AssertionError("This statement is false.");
}
}
and its unsafe publication:
//unsafe publication
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
The AssertionError could be thrown and I agree. The authors write that it is because of unsafe publication, but on the other hand there is no answer: what would be the proper way of publication? They indicates 4 safe publication idioms, but I do not understand, why would they work in the above case:
To publish an object safely, both the reference to the object and the object's state must be made visible to other threads at the same time. A properly constructed object can be safely published by:
- Initializing an object reference from a static initializer;
- Storing a reference to it into a volatile field or AtomicReference;
- Storing a reference to it into a final field of a properly constructed object;
- or Storing a reference to it into a field that is properly guarded by a lock.
I agree with 1&4, but have doubts why following publications would work:
//safe publication
public volatile Holder holder;
or
//safe publication
public final Holder holder;
volatile & final have impact only for the reference, not for the referenced object state, so I think the AssertionError would be still possible, right?
Instead of publication refinement, Authors show how to make the Holder immune for the unsafe publication, by:
private final int n;
I am curious if the following would also work? How is it connected with (effective) immutability?
private volatile int n;
It is my first question here, thank you for your help!
回答1:
Actually I think that volatile is the simplest to explain here. Unsafe publication happens when operations can be reordered and volatile prevents that. I could explain more probably, but it's already explained far more accurate than I will do.
Basically underneath there will be proper memory barriers inserted that will prevent re-orderings, as explained here. Essentially, what volatile does, is that if a ThreadA reads a volatile update performed by ThreadB, it is guaranteed to also see all the updates that were done before that volatile write.
final makes things safe too and it's specifically written in the JLS.
But there are two cases here according to : Storing a reference to it into a final field of a properly constructed object.
So according to the JLS, this is safe publication:
class Holder {
private final int n; // making final here
}
There are proper memory barriers inserted btw that prevent stores in the constructor to be re-ordered with publishing the reference itself.
What about this example?
static class Holder {
private int n;
public void setN(int n){
this.n = n;
}
}
And somewhere else:
class Other {
final Holder holder;
Other(){
holder = new Holder();
holder.setN(12);
}
}
It looks like this is still safe publication according to this
回答2:
Make the integer volatile & synchronize it to a locking object with the thread in which you are concurring with.
This isn't the exact code but more of an idea for you to wrap your head around. No two things can operate on one thing at once. Thats what causes deadlocks in programs and even operating systems.
Class1:
public static final Object lock = new Object();
private Holder holder;
public abstract void method1(); //Assume these two go to different places
public abstract void method2(); //At different times w/ different implementations
Thread1:
public void method1() {
synchronized(Class1.lock) {
holder.assertMadness();
}
}
Thread2:
public void method2() {
synchronized(Class1.lock) {
holder.assertMadness();
}
}
来源:https://stackoverflow.com/questions/50505349/safe-publication-of-mutable-object