Improper publication of Java Object Reference
Asked Answered
N

4

27

The below example is from the book "Java Concurrency in Practice" by Brian Goetz, Chapter 3, Section 3.5.1. This is an example of Improper publication of objects:

class SomeClass {
    public Holder holder;

    public void initialize() {
        holder = new Holder(42);
    }
}

public class Holder {
  private int n;
  public Holder(int n) { this.n = n; }

  public void assertSanity() {
    if (n != n)
      throw new AssertionError("This statement is false");
  }
}

It says that the Holder could appear to another thread in an inconsistent state and another thread could observe a partially constructed object. How can this happen? Could you give a scenario using the above example?

Also it goes on to say that there are cases when a thread may see a stale value the first time it reads a field and then a more up to date value the next time, which is why the assertSanity can throw AssertionError. How can the AssertionError be thrown?

From further reading, one way to fix this problem is to make Holder immutable by making the variable n final. For now, let us assume that Holder is not immutable but effectively immutable.

To safely publish this object, do we have to make holder initialization static and declare it as volatile (both static initialization and volatile or just volatile)?

Something like this:

public class SomeClass {
    public static volatile Holder holder = new Holder(42);
}
Nay answered 19/4, 2013 at 15:4 Comment(8)
About all I can see is the possibility of inconsistent cache state between two processors in a multiprocessor situation. That's always a possibility in a non-tightly-coupled MP environment, unless you take explicit steps to synchronize.Olibanum
ibm.com/developerworks/java/library/j-jtp0618 might be useful.Trilbee
@PaulGrime - After a brief review I didn't see anything there that addresses the above scenario. The reference is not "escaping" before the object is constructed. The int n is not public and cannot be viewed outside of the class.Olibanum
No, but holder is public, and another thread may call assertSanity on it before its constructor and field initialisation have completed - https://mcmap.net/q/357597/-why-is-double-checked-locking-broken-in-java. And maybe - https://mcmap.net/q/357598/-java-concurrency-in-practice-sample-14-12.Trilbee
@PaulGrime - The reference to the created object is not assigned to holder until after the constructor has returned (if the JITC is compliant).Olibanum
The fields would need to be final or volatile to guarantee that they are fully initialized after the constructor has returned.Aimee
@Aimee Actually, declaring the member field volatile still doesnt guarantee publication prior to holder being visible. You can look at ConcurrentHashMap's private method readUnderLock in which that takes this nuance into account. Though declaring holder as volatile does.Hsining
@JohnVint: I wanted to clarify your above comment, hence raised the question. PeterLawrey seems to disagree on this. #39053929 . Would really request you to voice your opinion.Nagaland
H
17

You can imagine creation of an object has a number of non-atomic functions. First you want to initialize and publish Holder. But you also need to initialize all the private member fields and publish them.

Well, the JMM has no rules for the write and publication of the holder's member fields to happen before the write of the holder field as occurring in initialize(). What that means is that even though holder is not null, it is legal for the member fields to not yet be visible to other threads.

You may end up seeing something like

public class Holder {
    String someString = "foo";
    int someInt = 10;
}

holder may not be null but someString could be null and someInt could be 0.

Under an x86 architecture this is, from what I know, impossible to happen but may not be the case in others.

So next question may be "Why does volatile fix this?" The JMM says that all writes that happen prior to the volatile store are visible to all subsequent threads of the volatile field.

So if holder is volatile and you see holder is not null, based on volatile rules, all of the fields would be initialized.

To safely publish this object, do we have to make holder initialization static and declare it as volatile

Yes, because as I mentioned if the holder variable is not null then all writes would be visible.

How can the AssertionError be thrown?

If a thread notices holder not to be null, and invokes AssertionError upon entering the method and reading n the first time may be 0 (the default value), the second read of n may now see the write from the first thread.

Hsining answered 19/4, 2013 at 15:48 Comment(3)
Thanks for your detailed answer, it makes a lot of sense. So are you saying that holder needs to be both volatile and statically initialized or just volatile is enough?Nay
In-line static initialization is actually ok without volatile (if that's the case just make it final), this is because class initialization (not object publication) happens-before the class can be used. If it weren't static then volatile + not-null check would be sufficient.Hsining
To elaborate a bit on this is because class initialization (not object publication) happens-before the class can be used All writes that occur prior to a class fully being initialized are visible to threads once the class is able to be used.Hsining
A
3
public class Holder {
  private int n;
  public Holder(int n) { this.n = n; }

  public void assertSanity() {
    if (n!=n)
      throw new AssertionError("This statement is false");
  }
}

Say one thread creates an instance of Holder, and passes the reference to another thread, which calls assertSanity.

The assignment to this.n in the constructor occurs in one thread. And two reads of n occur in another thread. The only happens-before relation here is between the two reads. There is no happens-before relation involving the assignment and any of the reads.

Without any happens-before relations, statements can be reordered in various ways, so from the perspective of one thread, this.n = n can occur after the constructor has returned.

This means that the assignment can appear to occur in the second thread after the first read and before the second, resulting in inconsistent values. The can be prevented by making n final, which guarantees that the value is assigned before the constructor finishes.

Aimee answered 19/4, 2013 at 16:11 Comment(0)
D
0

The problem which you ask about is caused by JVM optimizations and the fact that simple object creation:

MyClass obj = new MyClass()

isn't always done by steps:

  1. Reserve memory for new instance of MyClass on the Heap
  2. Execute constructor to set internal properties values
  3. Set 'obj' reference to address on the Heap

For some optimization purposes JVM can do it by steps:

  1. Reserve memory for new instance of MyClass on the Heap
  2. Set 'obj' reference to address on the Heap
  3. Execute constructor to set internal properties values

So, imagine if two threads want to access MyClass object. First one creates it but due to JVM it executes 'optimized' set of steps. If it will execute only step 1 and 2 (but wont do 3) than we can have a serious problem. If second thread uses this object (it wont be null because it already points to reserved part of memory on the Heap) than it's properties will be incorrect which can lead to nasty things.

This optimization wont happen if reference will be volatile.

Delapaz answered 13/9, 2016 at 13:59 Comment(0)
T
-1

The Holder class is OK, but the class someClass can appear in an inconsisten state - between creation and the call to initialize() the holder instance variable is null.

Thoracoplasty answered 19/4, 2013 at 16:0 Comment(1)
Yeah, TIL; but I can't downvote my own post and don't want to delete it... Oh well.Thoracoplasty

© 2022 - 2024 — McMap. All rights reserved.