В прошлом выпуске мы познакомили с основными принципами HashMap, для тех, кто не видел, можете перейти по ссылке ниже:
В этом выпуске мы расскажем о фатальных проблемах, которые могут возникнуть у HashMap в среде с высокой степенью параллелизма.
Возможности HashMap ограничены. Когда HashMap достигает определенного насыщения после вставки нескольких элементов, вероятность конфликта в позиции сопоставления ключей будет постепенно увеличиваться.
В это время HashMap необходимо расширить свою длину, т. е. выполнитьResize.
На появление Resize влияют два фактора:
1.Capacity
Текущая длина HashMap. В прошлом выпуске я сказал, что длина HashMap равна степени двойки.
2.LoadFactor
Коэффициент загрузки HashMap, значение по умолчанию — 0,75f.
Условия для измерения изменения размера HashMap следующие:
HashMap.Size >= Capacity * LoadFactor
1. Расширение
Создайте новый пустой массив Entry, длина которого вдвое превышает размер исходного массива.
2.ReHash
Пройдите по исходному массиву Entry и повторно хэшируйте все Entry в новый массив. Зачем повторно хэшировать? Потому что после увеличения длины меняются и правила Hash.
Давайте рассмотрим формулу хеша:
индекс = хэш-код (Key) & (Length - 1)
Когда исходная длина массива равна 8, операция хеширования представляет собой операцию И со 111 байтами; новая длина массива равна 16, операция хэширования представляет собой операцию И со 1111 байтами. Результаты хеширования явно отличаются.
HashMap перед изменением размера:
HashMap после изменения размера:
Java-код ReHash выглядит следующим образом:
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
Примечание. Следующий контент очень утомляет мозги, пожалуйста, сидите смирно и поддержите своих друзей.
Предположим, что HashMap достиг критической точки Resize. На данный момент есть два потока A и B, которые одновременно выполняют операцию Put на HashMap:
В этот момент достигается условие Resize, и каждый из двух потоков выполняет первый шаг Rezie, то есть расширение:
В это время оба потока достигли шага ReHash. Давайте рассмотрим код для ReHash:
Если в это время поток B переходит к объекту Entry3 сразу после выполнения строки кода в красном поле, поток приостанавливается. Для потока Б:
e = Entry3
next = Entry2
В это время поток A беспрепятственно выполняет ReHash. Когда ReHash завершен, результат выглядит следующим образом (e и next на рисунке представляют две ссылки на поток B):
До этого момента ничего не выглядит не так. Затем поток B возобновляет работу и продолжает выполнять свой собственный ReHash. Состояние потока B только что:
e = Entry3
next = Entry2
Когда приведенная выше строка выполняется, очевидно, что i = 3, потому что результат хеширования потока A для Entry3 также равен 3.
Мы продолжаем выполнять эти две строки, Entry3 помещается в массив потока B с индексом 3, аe указывает на Entry2. В это время точки e и next выглядят следующим образом:
e = Entry2
next = Entry2
Общая ситуация показана на рисунке:
Затем происходит новый раунд цикла, и выполняется строка кода в красном поле:
e = Entry2
next = Entry3
Общая ситуация показана на рисунке:
Затем выполните следующие три строки, чтобы вставить Entry2 в головной узел массива потока B, используя метод вставки заголовка:
Общая ситуация показана на рисунке:
Запускается третий цикл, и снова выполняется код в красном поле:
e = Entry3
next = Entry3.next = null
На последнем шаге, когда мы выполняем следующую строку, наступает момент стать свидетелем чуда:
newTable[i] = Entry2
e = Entry3
Entry2.next = Entry3
Entry3.next = Entry2
Связанный список появляется в цикле!
Общая ситуация показана на рисунке:
На данный момент проблема не возникла напрямую. При вызове Get для поиска несуществующего ключа, а результат Hash этого ключа точно равен 3, поскольку позиция 3 имеет круговой связанный список, программа войдетбесконечный цикл!
Эта ситуация не может не напоминать людям классический вопрос на собеседовании:
Комический алгоритм: как определить, есть ли цикл в связном списке?
1. При вставке слишком большого количества элементов необходимо изменить размер хэш-карты.
HashMap.Size >= Capacity * LoadFactor.
2.Хэш-картаИзменение размера включает два шага: расширение и ReHash. ReHash может формировать кольцо связанного списка в случае параллелизма.
----КОНЕЦ----
Друзья, которым понравилась эта статья, нажмите и удерживайте изображение, чтобы следить за номером подписки.программист маленький серый, смотрите больше захватывающего контента