In ConcurrentHashMap
of JDK 8, the methods tabAt
and setTabAt
are used to provide volatile read/write of the first element of bins in Node<K,V>[] table
. However, the authors comments 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.
I wonder if release ordering here means the happens-before relationship (an unlock of a monitor happens-before every subsequent lock of that same monitor) guaranteed by synchronized
. And if so, why setTabAt
is considered to be conservative, but not mandatory, given that the calls to tabAt
exist not only inside, but also outside synchronized
blocks? For example:
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//...
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// -> tabAt called here, outside the synchronized block
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
// -> tabAt called here, inside the synchronized block
if (tabAt(tab, i) == f) {
// do insertion...
}
}
}
}
}
Another question is that in the code above, is the call to tabAt
inside the synchronized
block necessary? In my understanding, the monitor lock already takes care of the memory visibility between threads, for example:
- Suppose that there's only one element in the bin, say Node-0
- Thread-A wants to insert Node-1 in the bin, right after Node-0 it founds by calling
tabAt
outside thesynchronized
block - But before Thread-A can lock on Node-0, Thread-B locks Node-0 and deletes it (by calling
setTabAt
) - Thread-A acquires the lock of Node-0 after Thread-B has released the lock
- As the happens-before relationship between Thread-A and Thread-B is guaranteed by the monitor lock, in this case, it seems to me that there's no need to call
tabAt
(which in turn callsUnsafe.getObjectVolatile
) to access and re-check the element.
Any help would be greatly appreciated.