Почему ConcurrentHashMap использует локальную переменную `tab` для ссылки на таблицу? - PullRequest
5 голосов
/ 15 апреля 2020

In ConcurrentHashMap.putVal() (версия JDK: 11; ConcurrentHashMap.java; строка 1010)

final V putVal(K key, V value, boolean onlyIfAbsent) {
   if (key == null || value == null) throw new NullPointerException();
   int hash = spread(key.hashCode());
   int binCount = 0;
   for (Node<K,V>[] tab = table;;) {
       ...
   }
   addCount(1L, binCount);
   return null;
}

Почему для ссылки на таблицу используется переменная tab? Аналогично в ConcurrentHashMap.get() (начиная со строки 934)

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;
}

Ответы [ 2 ]

4 голосов
/ 15 апреля 2020

Если вы используете table, экземпляр, на который он указывает, может измениться при работе с ним, что может привести к неопределенному поведению или исключениям. Таким образом, вам необходимо «зафиксировать» его локально и использовать эту локальную переменную.

I Предположим, это сделано для предотвращения неопределенного поведения, если оно, что не должно быть сделано, используется двумя потоки сразу в режиме записи *. Экземпляр, на который указывает table, может измениться даже в непараллельном HashMap.

Альтернативой этому может быть использование ключевого слова synchronized, но это снижает производительность.

* Вы можете читать из HashMap в несколько потоков без проблем, если им не манипулируют, пока его удерживают несколько потоков.

1 голос
/ 15 апреля 2020

Легче понять, почему Java делает это в HashMap, где метод resize() устанавливает table = newTab. Любой метод, который считывал table во время операции resize(), извлек бы ссылку из-под них и переназначил ее, что привело бы к непредсказуемому поведению.

Volatile могло бы обеспечить обновление метода чтения с помощью последний table; но это совсем не то, что мы хотим. Мы хотим, чтобы метод чтения продолжался непрерывно со значениями, которые были в table, когда он начал читать.

Synchronized может блокировать чтение и запись одновременно, но с ухудшением производительности. Если бы мы этого хотели, мы могли бы вернуться к использованию Hashtable.

То же самое основание c применимо к ConcurrentHashMap и его более сложному методу transfer(), который также переназначает ссылку table. Ссылка копируется в локальную переменную, чтобы не потерять ее при переназначении.

...