4

From the book Java Concurrency In Practice:

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.

My question is:

Why does the bullet point 3 have the constrain:"of a properly constructed object", but the bullet point 2 does not have?

Does the following code safely publish the map instance? I think the code meets the conditions of bullet point 2.

public class SafePublish {

    volatile DummyMap map = new DummyMap();

    SafePublish() throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // Safe to use 'map'?
                System.out.println(SafePublish.this.map);
            }
        }).start();
        Thread.sleep(5000);
    }

    public static void main(String[] args) throws InterruptedException {
        SafePublish safePublishInstance = new SafePublish();
    }
 

public class DummyMap {
    DummyMap() {
        System.out.println("DummyClass constructing");
      }
  }
} 

The following debug snapshot pic shows the map instance is null at the time of the execution is entering the construction of SafePublish. What happens if another thread now trying to read the map reference? Is it safe to read?

enter image description here

Jason
  • 521
  • 2
  • 7
  • I don't understand the question. The "properly constructed object" applies to all 4 bullets. – Andreas Jun 13 '21 at 10:05
  • 1
    *FYI:* "here prints null" is not because of thread-safety issues regarding a "properly constructed object". It is purely the result of the fact that `this.map` hasn't been assigned a value yet, since that won't happen until 5 seconds later. – Andreas Jun 13 '21 at 10:08
  • 1
    @Andreas There are TWO "properly constructed object"s, the first one is the object which being published, the second one is the object whose field is the published object. Please read closely. – Jason Jun 13 '21 at 10:15
  • 1
    Doesn't matter. As I already said, the fact that the field is `null` has nothing whatsoever to do with thread-safety. The field is `null` because **the field hasn't been assigned yet**. How could the code print anything other than null? What non-null value would that be? – Andreas Jun 13 '21 at 10:18
  • @Andreas Then why the bullet 3 has the constrain:"of a properly constructed object"? – Jason Jun 13 '21 at 10:20
  • 1
    Because that is the condition. But this question is meaningless, because you're asking why the code isn't thread-safe, as "shown by your code", but the code doesn't show that, it just shows that a field that hasn't been assigned a value is `null` by default. The code doesn't prove anything, making the question based on flawed conclusions on your part. – Andreas Jun 13 '21 at 10:24
  • @Andreas Again, read my question closely, I am NOT asking why it is not thread safe, I am asking why the object is not been safely published. It is not the same, OK? – Jason Jun 13 '21 at 10:27
  • Which part of your code do you believe is showing that the object is not been safely published? And why do you believe that? --- According to the comment in the code, it seems that you believe that the printing of value `null` proves that bullet 2 is wrong, but it doesn't prove anything at all related to thread safety, *as I've already said twice before!* The only thing it proves is that field `map` is `null` before it is assigned, because the `print` statement runs almost immediately (in another thread), and **`map` isn't assigned a value until 5 seconds *later***. – Andreas Jun 13 '21 at 11:28
  • @Andreas Let me try to give you a simple example of 'not been safely published', just change my code `volatile HashMap map;` to `final HashMap map;` , run the code again, then you can observe the 'map' not been safely published, because it violates the bullet point 3. Got it? – Jason Jun 13 '21 at 11:39
  • Ok, if we change `volatile` to `final`, it still prints `null`, right? Which is for the exact same reason it prints `null` right now, i.e. **the field hasn't been assigned a value *YET!***. It doesn't prove anything about thread-safety. Can you understand at all that the `print` statement executes 5 seconds ***BEFORE*** the `this.map = map;` statement? Since it is printing the value *before* it is assigned, why would you ever expect it to print anything other than `null`? – Andreas Jun 13 '21 at 11:47
  • @Andreas Because that is what 'safe publication' mean. The bullet point 3 means you should NOT use the way I just gave you to publish the object 'map', because it is not 'safe publication'. – Jason Jun 13 '21 at 11:51
  • Safe publication doesn't mean that a field will magically become clairvoyant and know the value it'll be assigned 5 seconds in the *future*. The value is `null`, because that is the default value, and it'll keep having that value until it is changed, which happens **5 seconds later**. Please, please understand, that for the first 5 seconds of its life, the `map` field of the `this` object will have a `null` value. When you access that field value during those 5 seconds, from *any* thread, that is the value they will see, and rightly so, because that is the value the field actually has. – Andreas Jun 13 '21 at 11:58
  • @Andreas If it goes that way, then why can't we remove the constrain:"of a properly constructed object" from the bullet point 3? – Jason Jun 13 '21 at 12:11
  • If the field is `final`, and we move the assignment up before we start the thread, you'd expect the thread to see the assigned value, right? No, because `this` is not yet a properly constructed object, so there is no guarantee that another thread will see the assigned value of field `map`. That is only guaranteed to be the case after the constructor returns, making the `this` object "properly constructed". – Andreas Jun 13 '21 at 12:15
  • @Andreas Then the bullet point 2 should also need this "properly constructed" constrain, because there is also no guarantee that another thread will see the assigned value of field map before `this` properly constructed. – Jason Jun 13 '21 at 12:21
  • Yes there is, because that is the guarantee that `volatile` gives you: Any change to a `volatile` field is immediately visible to all threads. – Andreas Jun 13 '21 at 12:23
  • @Andreas So, If we move the assignment up, you will never see the `map` prints 'null' if it is `volatile` type, even at the time before `this` been properly constructed? – Jason Jun 13 '21 at 12:32
  • That is correct. – Andreas Jun 13 '21 at 13:01
  • @Andreas, (really, @bothofyou). Neither of you has mentioned that this object's _constructor_ is sharing a reference to the object with another thread. That is never "safe publication." Not even when it works. – Solomon Slow Jun 13 '21 at 13:10
  • @SolomonSlow @Andreas, For simplicity, I updated the code, does the current code safely publish the `map` instance? I think the current code meets the conditions of bullet point 2. Right? – Jason Jun 13 '21 at 14:05
  • The constructor in your updated example still shares a reference to the object with another thread. The Java Language Specification deems an object to be "fully initialized" only _after_ the constructor returns. But your constructor creates a new thread, and shares its `this` reference with the new thread _before_ the constructor returns. – Solomon Slow Jun 13 '21 at 14:16
  • Here's _one_ reason why that's bad: Suppose your new thread's `run()` method called `this.someMethod(...)`, and then suppose that some other programmer decides to implement `MyClass extends SafePublish` and they provide an override for `someMethod()`. Even if you can guarantee that it's safe for the new thread to call _your_ `someMethod()` before the constructor has returned, you can't guarantee that it's safe for the new thread to call _their_ `someMethod()`. – Solomon Slow Jun 13 '21 at 14:21
  • @Andreas based on you conclusion, in the above code, the construction of `volatile map` happens-before the construction of `SafePublish `, right? – Jason Jun 13 '21 at 14:22
  • It is totally incorrect that the initialized value is somehow set after the created thread in the constructor finishes its wait. It's set before the constructor is run (visible to thread constructor is running in), and there's a memory fence that guarantees the created thread will see it as well (see @Malt's very good answer below), although that lasat fact is somewhat less obvious. – BadZen Jun 13 '21 at 15:47
  • @SolomonSlow We know this, that's why we haven't mentioned it, specifically. It's what the "properly constructed object" is about, i.e. before the constructor returns, the object referenced by `this` is not "properly constructed". – Andreas Jun 13 '21 at 15:50
  • 1
    @Jason I don't know what you mean by "the construction of `SafePublish`". If you decompile the constructor, you will see that the compiler change it to `SafePublish() { super(); this.map = new DummyMap(); new Thread(...`, so as you can see, `map` is assigned *before* the code you wrote in the constructor, but it does happen **during** the execution of the constructor execution as a whole. – Andreas Jun 13 '21 at 16:00
  • @Andreas Yeah, i think you are right. Appreciate your sharing insight. – Jason Jun 13 '21 at 16:09

1 Answers1

7

It's because final fields are guaranteed to be visible to other threads only after the object construction while the visibility of writes to volatile fields are guaranteed without any additional conditions.

From jls-17, on final fields:

An 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.

on volatile fields:

A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).

Now, regarding your specific code example, JLS 12.5 guarantees that field initialization occurs before the code in your constructor is executed (see steps 4 and 5 in JLS 12.5, which is a bit too long to quote here). Therefore, Program Order guarantees that the constructor's code will see map initialized, regardless of whether it's volatile or final or just a regular field. And since there's a Happens-Before relation before field writes and the start of a thread, even the thread you're creating in the constructor will see map as initialized.

Note that I specifically wrote "before code in your constructor is executed" and not "before the constructor is executed" because that's not the guarantee JSL 12.5 makes (read it!). That's why you're seeing null in the debugger before the first line of the constructor's code, yet the code in your constructor is guaranteed to see that field initialized.

Malt
  • 28,965
  • 9
  • 65
  • 105
  • Thanks for the insight. Please refer the code I gave above, does the code safely publish the `map` instance? If so, then does the construction of the `volatile map` happens-before the construction of the class `SafePublish`? – Jason Jun 13 '21 at 14:29
  • 1
    It's an extreme example, but I think it's safe. If `map` was `final` it would have been safe too since the thread is started after the `map` field has been written. It wouldn't have been safe if `map` was final and the write to it was after the start of the thread. – Malt Jun 13 '21 at 14:32
  • remember that field initialization is done before the constructor's code is executed, so `volatile HashMap map = new HashMap();` is executed before any other code. In JLS-17 terms, there's a `Program-Order` relation between `volatile HashMap map = new HashMap();` and any code in the constructor. – Malt Jun 13 '21 at 14:34
  • I updated the code(added a class `DummyMap`), please check the debug snapshot pic I gave above, the `volatile map` instance is `null` at the time of the execution is entering the construction of `SafePublish`. Is it safe to use the `volatile map` instance at that time? – Jason Jun 13 '21 at 14:56
  • 1
    @Jason yes, it's safe. I've added an explanation to the answer. – Malt Jun 13 '21 at 15:23
  • Re, "Yes, it's safe." "Safe" in the sense that this exact example is guaranteed to work, but the constructor of OP's `SafePublish` class "leaks" the `this` pointer to another thread before the constructor has returned. That is generally a _Bad Idea._ (See my comments on the original question for one example of how it can come back to bite you later even if you've got it working perfectly today.) – Solomon Slow Jun 13 '21 at 15:38
  • @SolomonSlow Yes, it is a bad idea, I use this code only to check the bullet point 2 from the JCIP book(see above), because the point does not have the constrain "a properly constructed object", so I intentionally make the code with not "a properly constructed object" to check its correctness. – Jason Jun 13 '21 at 15:45
  • 1
    FYI, "safe" in concurrency always means something about the relative order of two (or more) R/W operations - never just "is this single operation safe". So when thinking about safety, identify the operations you are trying to order, and pose the question in those terms! – BadZen Jun 13 '21 at 15:51
  • 1
    The code in the question is definitely not something that should be done in practice, but I think it's meant as an illustrative example. Questions about the Java memory model are full of contrived examples that rarely occur in practice (or occur in much more complex ways that are hard to put in question form). Also, OP specifically asked about the safety of *publishing* in that way, not the general saneness of the above code. – Malt Jun 13 '21 at 16:39