Search code examples
javamultithreadingconcurrencyimmutabilityfinal

Synchronize to ensure that reference to immutable object will be seen by another thread


I was studying this to understand the behavior of final fields in the new JMM (5 onwards). This concept is clear: guaranteed visibility of initialized final fields to all threads after the object is properly constructed.

But then at the end of the section, I read this, which simply confuses me:

Now, having said all of this, if, after a thread constructs an immutable object (that is, an object that only contains final fields), you want to ensure that it is seen correctly by all of the other thread, you still typically need to use synchronization. There is no other way to ensure, for example, that the reference to the immutable object will be seen by the second thread.

Does this means that though individual final fields (that compose an immutable object) do not have synchronization(say, visibility here) issues. But the immutable object itself when first created in a thread may not be visible (as properly created) in other threads?

If so, though we can share initialized immutable objects across threads without any thread-un-safe worries, but at the time of creation, they need 'special care' for thread safety just like for other mutables?


Solution

  • The semantics of final fields, as defined in section 17.5 of the JLS, guarantee 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.

    In other words, it says that if a thread sees a completely initialized object, then it is guaranteed to see it's final fields correctly initialized.

    However, there's no guarantee about the object being visible to a given thread. It is a different problem.

    If you do not use some kind of synchronization to publish a reference of your object, then the other thread might never be able to see a reference to it.

    Consider the following code:

    final class A {
      private final int x;
      A(int x) { this.x = x; }
      public getX() { return x; }
    }
    
    class Main {
      static volatile A a1 = null;
      static A a2 = null;
      public static void main(String[] args) {
        new Thread(new Runnable() { void run() { try {
          while (a1 == null) Thread.sleep(50);
          System.out.println(a1.getX()); } catch (Throwable t) {}
        }}).start()
        new Thread(new Runnable() { void run() { try {
          while (a2 == null) Thread.sleep(50);
          System.out.println(a2.getX()); } catch (Throwable t) {}
        }}).start()
        a1 = new A(1); a2 = new A(1);
      }
    }
    

    Note that the a1 field is volatile. This ensures that, eventually, a write to this field will be made visible to all threads reading it some time later. The field a2 is not volatile (so, a write to this field by one thread might never get noticed by other threads).

    In this code, we can be sure that thread 1 will finish executing (that is, it will see that a1 != null. However, it might happen that thread 2 will halt, as it will never see the write to the field a2, since it is not volatile.