Нужно простое объяснение, как работает "strip striping" с ConcurrentHashMap

Согласно Java Concurrency на практике, в главе 11.4.3 говорится:

Блокировка разделения иногда может быть расширена до раздела блокировка на переменном множестве независимых объектов, и в этом случае он называется блокировкой. Например, реализация ConcurrentHashMap использует массив из 16 замков, каждый из которых защищает 1/16 хэш-ковшей; ведро N охраняется блокировкой N mod 16.

У меня все еще есть проблемы, чтобы понять и визуализировать механизм блокировки и ведра. Может ли кто-нибудь объяснить это словами с хорошим пониманием:)

Спасибо заранее.

Ответ 1

Хэш-карта построена на массиве, где хеш-функция сопоставляет объект с элементом в базовом массиве. Предположим, что базовый массив имеет 1024 элемента - ConcurrentHashMap фактически превращает это в 16 разных подархивов из 64 элементов, например. {0, 63}, {64, 127} и т.д. Каждый подматрица имеет свой собственный замок, поэтому модификация поддиапазона {0, 63} не влияет на подматрицу {64, 127} - один поток может записывать в первый вспомогательный массив, тогда как другой поток записывает во второй вспомогательный массив.

Ответ 2

Разница между блокировкой в ​​ Collections.synchronizedMap() и a ConcurrentHashMap выглядит следующим образом:

Если несколько потоков будут обращаться к Collections.synchronizedMap() часто, будет много конфликтов, поскольку каждый метод синхронизируется с использованием общей блокировки (т.е. если поток X вызывает метод на Collections.synchronizedMap(), все остальные потоки будут заблокированы от вызова любого метода на Collections.synchronizedMap(), пока поток X не вернется из метода, который он вызвал).

A ConcurrentHashMap имеет переменное количество блокировок (по умолчанию - 16), каждый из которых защищает сегмент ключей в ConcurrentHashMap. Таким образом, для ConcurrentHashMap с 160 ключами каждая блокировка будет защищать 10 элементов. Поэтому методы, работающие с ключом (get, put, set и т.д.), Блокируют доступ к другим методам, действующим на ключ, где ключи находятся в одном сегменте. Например, если поток X вызывает put(0, someObject), а затем поток Y вызывает put(10, someOtherObject), эти вызовы могут выполняться одновременно, а нить Y не должна ждать, пока поток X не вернется из put(0, someObject). Пример приведен ниже.

Кроме того, некоторые методы, такие как size() и isEmpty(), вообще не охраняются. Хотя это позволяет увеличить concurrency, это означает, что они не являются сильно согласованными (они не будут отражать состояние, которое изменяется одновременно).

public static void main(String[] args) {
  ConcurrentHashMap<Integer, Object> map = new ConcurrentHashMap<>(160);

  new Thread(new Runnable() {
    @Override
    public void run() {
      map.put(0, "guarded by one lock");
    }
  }.start();

  new Thread(new Runnable() {
    @Override
    public void run() {
      map.put(10, "guarded by another lock");
    }
  }.start();

  new Thread(new Runnable() {
    @Override
    public void run() {
      // could print 0, 1, or 2
      System.out.println(map.count());
    }
  }.start();
}

Ответ 3

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