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

Java 8 ConcurrentHashMap

Я заметил, что ConcurrentHashMap полностью переписан на Java 8, чтобы быть более "незаблокированным". Я просмотрел код метода 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;
}

Вопрос:

Как можно видеть из одного потока, изменения, сделанные для этого хэш файла из других потоков, поскольку код не находится под зонтиком синхронизации (который обеспечивал бы выполнение отношения "раньше" )?

Примечание. Вся ConcurrentHashMap является оболочкой таблицы: transient volatile Node<K,V>[] table;

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

4b9b3361

Ответ 1

Короткий ответ

Node#val - volatile, который устанавливает, что вы делаете это до его заказа.

Более длинный ответ

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

Полезно знать, что оригинальный ConcurrentHashMap тоже не блокирует. Обратите внимание, что pre-Java 8 CHM get

V get(Object key, int hash) {
    if (count != 0) { // read-volatile
        HashEntry<K,V> e = getFirst(hash);
        while (e != null) {
            if (e.hash == hash && key.equals(e.key)) {
                V v = e.value;
                if (v != null)
                    return v;
                return readValueUnderLock(e); // ignore this
            }
            e = e.next;
        }
    }
    return null;
}

В этом случае блокировки нет, так как это работает? HashEntry#value - volatile. Это точка синхронизации безопасности потоков.

Класс Node для CHM-8 тот же.

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;

Таким образом, ненулевой вал в этом случае должен гарантировать, что это произойдет - до отношения относительно действий до put.

Ответ 2

В документации не указано, что происходит синхронизация. Например, он указывает

[...] агрегированные операции, такие как putAll и clear, параллельные поисковые запросы могут отражать вставку или удаление только некоторых записей.

Другими словами, существует разница между возможностью одновременного использования и предоставлением синхронизированного доступа.

Ответ 3

Спецификация языка Java пишет:

Если у нас есть два действия x и y, мы пишем hb (x, y), чтобы указать, что x происходит до y.

  • Если x и y - действия одного и того же потока, а x - до y в порядке программы, тогда hb (x, y).

  • Для этого объекта существует конец до конца от конца конструктора объекта до начала финализатора (§12.6).

  • Если действие x синхронизируется со следующим действием y, то мы также имеем hb (x, y).

  • Если hb (x, y) и hb (y, z), то hb (x, z).

и определяет

Действия синхронизации вызывают синхронизированное отношение к действиям, определяемое следующим образом:

  • Действие разблокировки на мониторе m синхронизируется со всеми последующими действиями блокировки на m (где "последующее" определяется в соответствии с порядком синхронизации).

  • Запись в изменчивую переменную v (§8.3.1.4) синхронизируется со всеми последующими чтениями v любым потоком (где "последующее" определяется в соответствии с порядком синхронизации).

  • Действие, которое запускает поток, синхронизируется с первым действием в запущенном потоке.

  • Запись значения по умолчанию (ноль, ложь или нуль) для каждой переменной синхронизируется с первым действием в каждом потоке.

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

  • Последнее действие в потоке T1 синхронизируется с любым действием в другом потоке T2, который обнаруживает, что T1 завершен.

    T2 может выполнить это, вызвав T1.isAlive() или T1.join().

  • Если поток T1 прерывает поток T2, прерывание по T1 синхронизируется с любой точкой, где любой другой поток (включая T2) определяет, что T2 был прерван (путем исключения InterruptedException или путем вызова Thread.interrupted или Thread.isInterrupted).

То есть, считывание измененного поля устанавливается - до того, как явная блокировка.