Нужно простое объяснение, как работает "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
Ключевое понятие здесь - "ведро". вместо этого, используя глобальную блокировку для всей хэш-таблицы, она использует один небольшой замок для каждого ведра.
Это также хорошо похоже на сортировку ковша, что может улучшить сложность сортировки.