katiex7
katiex7

Reputation: 913

Confused about safe publishing and visibility in Java, especially with Immutable Objects

When I read the Java Concurrency in Practice by Brian Goetz, I recall him saying "Immutable objects, on the other hand, can be safely accessed even when synchronization is not used to publish the object reference" in the chapter about visibility.

I thought that this implies that if you publish an immutable object, all fields(mutable final references included) are visible to other threads that might make use of them and at least up to date to when that object finished construction.

Now, I read in https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html that "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. The guarantees the program gets from final fields should be carefully tempered with a deep and careful understanding of how concurrency is managed in your code."

They seem to contradict each other and I am not sure which to believe.

I have also read that if all fields are final then we can ensure safe publication even if the object is not per say immutable. For example, I always thought that this code in Brian Goetz's concurrency in practice was fine when publishing an object of this class due to this guarantee.

@ThreadSafe
public class MonitorVehicleTracker {
    @GuardedBy("this")
    private final Map<String, MutablePoint> locations;

    public MonitorVehicleTracker(
            Map<String, MutablePoint> locations) {
        this.locations = deepCopy(locations);
    }

    public synchronized Map<String, MutablePoint> getLocations() {
        return deepCopy(locations);
    }

    public synchronized MutablePoint getLocation(String id) {
        MutablePoint loc = locations.get(id);
        return loc == null ? null : new MutablePoint(loc);
    }

    public synchronized void setLocation(String id, int x, int y) {
        MutablePoint loc = locations.get(id);
        if (loc == null)
            throw new IllegalArgumentException("No such ID: " + id);
        loc.x = x;
        loc.y = y;
    }

    private static Map<String, MutablePoint> deepCopy(
            Map<String, MutablePoint> m) {
        Map<String, MutablePoint> result =
            new HashMap<String, MutablePoint>();
        for (String id : m.keySet())
            result.put(id, new MutablePoint(m.get(id)));
        return Collections.unmodifiableMap(result);
    }
}
public class MutablePoint { /* Listing 4.5 */ }

For example, in this code example, what if that final guarantee is false and a thread made an instance of this class and then the reference to that object is not null, but the field locations is null at the time another thread uses that class?

Once again, I don't know which is correct or if I happened to misinterpret both the article or Goetz

Upvotes: 4

Views: 1069

Answers (1)

user2982130
user2982130

Reputation:

This question has been answered a few times before but I feel that many of those answers are inadequate. See:

In short, Goetz's statement in the linked JSR 133 FAQ page is more "correct", although not in the way that you are thinking.

When Goetz says that immutable objects are safe to use even when published without synchronization, he means to say that immutable objects that are visible to different threads are guaranteed to retain their original state/invariants, all else remaining the same. In other words, properly synchronized publication is not necessary to maintain state consistency.

In the JSR-133 FAQ, when he says that:

you want to ensure that it is seen correctly by all of the other thread (sic)

He is not referring to the state of the immutable object. He means that you must synchronize publication in order for another thread to see the reference to the immutable object. There's a subtle difference to what the two statements are talking about: while JCIP is referring to state consistency, the FAQ page is referring to access to a reference of an immutable object.

The code sample you provided has nothing, really, to do with anything that Goetz says here, but to answer your question, a correctly initializedfinal field will hold its expected value if the object is properly initialized (beware the difference between initialization and publication). The code sample also synchronizes access to the locations field so as to ensure updates to the final field are thread-safe.

In fact, to elaborate further, I suggest that you look at JCIP listing 3.13 (VolatileCachedFactorizer). Notice that even though OneValueCache is immutable, that it is stored in a volatile field. To illustrate the FAQ statement, VolatileCachedFactorizer will not work correctly without volatile. "Synchronization" is referring to using a volatile field in order to ensure that updates made to it are visible to other threads.

A good way to illustrate the first JCIP statement is to remove volatile. In this case, the CachedFactorizer won't work. Consider this: what if one thread set a new cache value, but another thread tried to read the value and the field was not volatile? The reader might not see the updated OneValueCache. BUT, recalling that Goetz refers to the state of the immutable object, IF the reader thread happened to see an up-to-date instance of OneValueCache stored at cache, then the state of that instance would be visible and correctly constructed.

So although it is possible to lose updates to cache, it is impossible to lose the state of the OneValueCache if it is read, because it is immutable. I suggest reading the accompanying text stating that "volatile reference used to ensure timely visibility."

As a final example, consider a singleton that uses FinalWrapper for thread safety. Note that FinalWrapper is effectively immutable (depending on whether the singleton is mutable), and that the helperWrapper field is in fact non-volatile. Recalling the second FAQ statement, that synchronization is required for access the reference, how can this "correct" implementation possibly be correct!?

In fact, it is possible to do this here because it is not necessary for threads to immediately see the up-to-date value for helperWrapper. If the value that is held by helperWrapper is non-null, then great! Our first JCIP statement guarantees that the state of FinalWrapper is consistent, and that we have a fully initialized Foo singleton that can be readily returned. If the value is actually null, there are 2 possibilities: firstly, it is possible that it is the first call and it has not been initialized; secondly, it could just be a stale value.

In the case that it is the first call, the field itself is checked again in a synchronized context, as suggested by the second FAQ statement. It will find that this value is still null, and will initialize a new FinalWrapper and publish with synchronization.

In the case that it is just a stale value, by entering the synchronized block, the thread can setup a happens-before order with a preceding write to the field. By definition, if a value is stale, then some writer has already written to the helperWrapper field, and that the current thread just has not seen it yet. By entering into the synchronized block, a happens-before relationship is established with that previous write, since according to our first scenario, a truly uninitialized helperWrapper will be initialized by the same lock. Therefore, it can recover by rereading once the method has entered a synchronized context and obtain the most up-to-date, non-null value.

I hope that my explanations and the accompanying examples that I have given will clear things up for you.

Upvotes: 5

Related Questions