В ConcurrentHashMap
JDK 8 методы tabAt
и setTabAt
используются для обеспечения энергозависимого чтения / записи первого элемента бинов в Node<K,V>[] table
. Однако авторы отмечают, что:
Обратите внимание, что вызовы
setTabAt
всегда происходят в заблокированных областях, и поэтому в принципе требуется только порядок выпуска, а не полная семантика изменчивости, но в настоящее время они кодируются как изменчивые записи, чтобы быть консервативными.
Интересно, означает ли здесь порядок выпуска связь «происходит до» (разблокировка монитора происходит до каждой последующей блокировки того же монитора), гарантированную synchronized
. И если да, то почему setTabAt
считается консервативным, но не обязательным, учитывая, что вызовы tabAt
существуют не только внутри, но и за пределами synchronized
блоков? Например:
/** 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...
}
}
}
}
}
Другой вопрос, что в приведенном выше коде необходим вызов tabAt
внутри блока synchronized
? Насколько я понимаю, блокировка монитора уже заботится о видимости памяти между потоками, например:
- Предположим, что в корзине только один элемент, скажем, Узел 0.
- Поток A хочет вставить узел 1 в корзину сразу после узла 0, который он находит, вызывая
tabAt
вне блокаsynchronized
. - Но прежде чем Thread-A сможет заблокировать узел 0, Thread-B блокирует узел 0 и удаляет его (вызывая
setTabAt
) - Поток-A получает блокировку узла-0 после того, как поток-B освободил блокировку
- Поскольку связь между потоком-A и поток-B "происходит раньше", гарантируется блокировкой монитора, в этом случае мне кажется, что нет необходимости вызывать
tabAt
(который, в свою очередь, вызываетUnsafe.getObjectVolatile
) для доступа и повторной проверки элемент.
Любая помощь будет принята с благодарностью.