Safe publication and ConcurrentHashMap

Let's assume we have a class Container

class Container {
      private final Map<LongHolder, Integer> map = new ConcurrentHashMap<>();
      static class LongHolder {
         private final Long i;
         private LongHolder(Long i) {
            this.i = i;
         }
      }

      public LongHolder createValue(Long i) {
          LongHolder v = new LongHolder(i);
          map.put(v, 1)
          return LongHolder;
      }

      public void doSmth(LongHolder longHolder) {
         map.get(longHolder);
         ... // do smth
      }
}

Can operations be reordered in a such manner that the reference to LongHolder from createValue escapes the method and thus becomes avaliable for other threads before putting it into the map? In this case we can acquire a LongHolder refernce form createValue and pass it to doSmth in the other thread which won't see it in the map. Is it possible? If not please explain.

Update: The Javadoc of ConcurrentHashMap states Retrievals reflect the results of the most recently completed update operations holding upon their onset. (More formally, an update operation for a given key bears a happens-before relation with any (non-null) retrieval for that key reporting the updated value.) But there is of the most recently completed update operations the question was exactly about this subtle case when reordering potentially could happen in the way that actually update operation wasn't complete and reference escaped before put.The Javadoc of ConcurrentHashMap states only the fact that given key bears a happens-before relation with any (non-null) retrieval for that key in other words only if we get retrieval for that key we can debate about happens before relations with update operation for this key.

I can only suggest few other reasons why it can't happen:

1) that the reordering rules are more strict than I thought and we can't assume an arbitral reordering(or can we? and what are the rules?).

2) that put has some magic properties that don't allow reodering and return happens only after completed put. What are those properties in that case?

Upvotes: 0

Views: 116

Answers (1)

Stephen C
Stephen C

Reputation: 719346

Let assume that threads A and B have a reference to a Container instance.

(Since Container.map is declared as final, its value will be safely published. Furthermore, since it refers to a ConcurrentHashMap, no synchronization is required to maintain thread-safety.)

Now suppose thread A calls Longholder holder = container.createValue(x) and somehow passes holder to thread B. Your question is, if B calls doSmth passing that holder, will the map.get(holder) call see it in the map?

The answer is Yes, it will.

The specification for ConcurrentHashMap says this:

"Retrievals reflect the results of the most recently completed update operations holding upon their onset. (More formally, an update operation for a given key bears a happens-before relation with any (non-null) retrieval for that key reporting the updated value.)".

That means there is a happens before from the put call that thread A made and the subsequent get call in thread B. That in turn means that:

  • the get will find the LongHolder key, and
  • the get will return the correct value.

The value of the LongHolder object's i field will also be as expected, and it would be even if LongHolder was a mutable holder.

(Note that there is not much point declaring an immutable LongHolder here. It is equivalent to a java.lang.Long ... albeit with a simpler API.)


Can operations be reordered in a such manner that the reference to LongHolder from createValue escapes the method and thus becomes available for other threads before putting it into the map?

Basically, no. The relevant operations are this:

      LongHolder v = new LongHolder(i);
      map.put(v, 1)
      return v;    // corrected typo
  1. There is no reordering of the source code that makes any sense.
  2. If you are talking about reordering by compiler with respect to the thread that is running createValue, the JLS does not permit it. Any reordering that alters intra-thread visibility is forbidden.
  3. The publication of v via the map is a safe publication, due to the properties of get and put. (At the implementation, there are memory barriers that forbid the harmful reorderings around the get and put calls.)

  4. and 3. are a consequence of the happens before relationships.

Now, if the code was to change to this:

      LongHolder v = new LongHolder(i);
      map.put(v, 1)
      // Somehow modify v.i
      return v;

then that would be a form of unsafe publication. Since the change to v.i by A happens after the put, the happens before relation between the put and the get in B is no sufficient to guarantee to make the new value of v.i visible in thread B. That is because the chaining doesn't work anymore.

Now, I suppose that if the result of the createValue call in thread A was passed to another thread (B or C) in an unsafe way, then the latter is not guaranteed to see the correct value for v.i ... if LongHolder is mutable. But that is not a problem with the createValue / doSmth code. And the publication of the value of v via the map is safe.

But I reckon that this discussion of reorderings is missing the point. Any reordering that violates the visibility semantics guaranteed by the memory model is forbidden. The JIT compiler is not allowed to do it. So you just need to do the happens before analysis.

Upvotes: 2

Related Questions