4

In ConcurrentHashMap of JDK8, both of the methods tabAtand setTabAt need volatile semantics.

static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}

static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}

The author commented that:

Note that calls to setTabAt always occur within locked regions, and so in principle require only release ordering, not full volatile semantics, but are currently coded as volatile writes to be conservative.

Then in ConcurrentHashMap of JDK11, both of the methods tabAt and setTabAt don't need volatile semantics and use acquire and release semantics instead.

static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectAcquire(tab, ((long)i << ASHIFT) + ABASE);
}

static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    U.putObjectRelease(tab, ((long)i << ASHIFT) + ABASE, v);
}

The author commented that:

Note that calls to setTabAt always occur within locked regions, and so require only release ordering.

As far as I know, the elements of the array Node<K,V>[] tab aren't volatile. Using only acquire and release semantics can't guarantee immediate visibility. The thread that gets elements can't immediately see the update made by the thread that sets elements.

Even if the calls to setTabAt always occur within locked regions, it seems that it doesn't make any difference. Because the calls to tabAt don't always occur within locked regions, there is no guarantee of happens-before relationship.

The call to tabAt doesn't occur within a locked region in the method get

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

I wonder if there is something wrong with my understanding. Could anyone provide more information about this?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Oliver
  • 41
  • 4
  • 1
    Why do you think that an acquire→release relationship doesn’t guaranty memory visibility? – Holger May 08 '23 at 16:50
  • "Using only acquire and release semantics can't guarantee visibility" - it does, but it's weaker then volatile. very good read [over here](https://stackoverflow.com/questions/46889610/sequential-consistency-volatile-explanation) – Eugene May 08 '23 at 17:46
  • Maybe I didn’t describe it accurately. When I say using only acquire and release semantics can't guarantee visibility, I mean that the thread that calls `getObjectVolatile` can't immediately see the update made by the thread that calls `putObjectRelease`.@Holger @Eugene – Oliver May 09 '23 at 01:34
  • 1
    Depends on what you mean with “immediately”. There are no timing guarantees at all. But opaque and stronger memory accesses all have the same visibility regarding the writes and reads of the same variable. When it comes to transitivity relations, acquire/release is as good as volatile; the discussion about locking is about the ordering of concurrent `compute…` operations, not about `get` calls. – Holger May 09 '23 at 08:20
  • In `jdk.internal.misc.Unsafe` of JDK11, the author mentioned something about immediate visibility in the comment of the method `putObjectRelease`. ```Versions of {@link #putObjectVolatile(Object, long, Object)} that do not guarantee immediate visibility of the store to other threads. This method is generally only useful if the underlying field is a Java volatile (or if an array cell, one that is otherwise only accessed using volatile accesses). Corresponds to C11 atomic_store_explicit(..., memory_order_release).``` – Oliver May 09 '23 at 09:19
  • 1
    This comment originally belonged to `putOrderedObject` back then, when there were no acquire/release/opaque and not even a “getOrderedObject”, so the counterpart had to be a volatile read when the comment was written. I don’t know why they decided to keep the comment dangling somewhere above the put methods (it’s *not* a documentation comment for `putReferenceRelease`) and change the first sentence to plural while not changing the second. “otherwise only accessed using volatile accesses” should have been updated to “otherwise only accessed using opaque or stronger accesses”. – Holger May 09 '23 at 10:35
  • 1
    On the other hand, it’s neither an official API nor a javadoc comment that would appear anywhere. Since the official API (`VarHandle`) doesn’t provide a documentation of these access mode either (and even the JMM doesn’t cover them), the best you can get is [this document](https://gee.cs.oswego.edu/dl/html/j9mm.html). – Holger May 09 '23 at 10:46
  • 1
    When you look at [the summary at the end](https://gee.cs.oswego.edu/dl/html/j9mm.html#summarysec) you find “• Opaque mode …ensures awareness of variables across threads • Release/Acquire mode additionally supports… • Volatile mode additionally supports…” In short, opaque is enough for visibility (of a single variable) while the other modes provide additional guarantees regarding ordering, which is necessary to see compound objects in a consistent state or establish a *happens-before* relationship. It would be strange, if a release was not combinable with an acquire but required a volatile read – Holger May 09 '23 at 10:51

0 Answers0