Подтвердить что ты не робот

Понимание кода метода вычисления ConcurrentHashMap

Просто нашел этот странный код в методе вычисления ConcurrentHashMap: (строка 1847)

public V compute(K key,
                 BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    ...
    Node<K,V> r = new ReservationNode<K,V>();
    synchronized (r) {   <--- what is this?
        if (casTabAt(tab, i, null, r)) {
            binCount = 1;
            Node<K,V> node = null;

Таким образом, код выполняет синхронизацию по новой переменной, доступной только для текущего потока. Это означает, что нет никакой другой нити, которая могла бы конкурировать за этот замок или вызвать эффекты барбариса памяти.

В чем смысл этого действия? Является ли это ошибкой или вызывает некоторые неочевидные побочные эффекты, о которых я не знаю?

p.s. jdk1.8.0_131

4b9b3361

Ответ 1

casTabAt(tab, i, null, r)

публикует ссылку на r.

static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

Поскольку c помещается в tab, возможно, к нему обращается другой поток, например. в putVal. Таким образом, этот блок synchronized необходим, чтобы исключить другие потоки от выполнения других синхронизированных вещей с помощью этого Node.

Ответ 2

Пока r является новой переменной в этой точке, она сразу же помещается во внутреннюю table через if (casTabAt(tab, i, null, r)), после чего другой поток может получить к ней доступ в разных частях кода.

Внутренний комментарий не-javadoc описывает его таким образом

Вставка (через put или ее варианты) первого node в пустую корзину выполняется просто с помощью CASing в корзину. Это, безусловно, самое общий случай для операций ввода в большинстве ключевых/хэш-распределений. Другие операции обновления (вставка, удаление и замена) требуют блокировок. Мы не хотим тратить пространство, необходимое для заблокируйте объект с каждым бункером, поэтому вместо этого используйте первый node списка bin как замок. Поддержка блокировки этих замков зависит от встроенных "синхронизированные" мониторы.

Ответ 3

Всего 0.02 $здесь

То, что вы показали на самом деле, это просто ReservationNode - это означает, что корзина пуста и что зарезервировано какое-то предложение Node. Обратите внимание, что этот метод позже заменяет этот Node на реальный:

 setTabAt(tab, i, node);

Итак, это делается для того, чтобы замена была атомной, насколько я понимаю. После публикации через casTabAt и если другие потоки видят его - они не могут синхронизироваться с ним, поскольку блокировка уже сохранена.

Также обратите внимание, что, когда в ячейке есть запись, первый Node используется для синхронизации (далее в методе):

boolean added = false;
            synchronized (f) { // locks the bin on the first Node
                if (tabAt(tab, i) == f) {
......

Как и сторона node, этот метод изменился в 9, так как 8. Например, запуск этого кода:

 map.computeIfAbsent("KEY", s -> {
    map.computeIfAbsent("KEY"), s -> {
        return 2;
    }
 })

никогда не закончил бы в 8, но бросил бы Recursive Update в 9.