Помимо бесконечного цикла, что не так с HashMap?

Java задняя часть опрос
Помимо бесконечного цикла, что не так с HashMap?

«Это первый день моего участия в первом испытании обновлений 2022 года. Подробную информацию о мероприятии см.:Вызов первого обновления 2022 г.".

Этот вопрос в этой статье является открытым вопросом: помимо бесконечного цикла, какие еще проблемы есть у HashMap? Вообще говоря, все "проблемы" HashMap вызваны неправильным использованием (HashMap), эти проблемы можно условно разделить на две категории:

  1. Проблемы с программой: например, в JDK 1.7 HashMap во время параллельной вставки может возникнуть проблема с бесконечным циклом или перезаписью данных.
  2. Бизнес-проблемы: например, неупорядоченность HashMap приводит к тому, что результаты запроса не соответствуют ожидаемым результатам.

Давайте посмотрим на них один за другим.

1. Проблема бесконечного цикла

Проблема с бесконечным циклом возникла в версии JDK 1.7. Причина в том, что JDK 1.7 HashMap использует метод вставки заголовка, поэтому это может вызвать проблему бесконечного цикла во время одновременного расширения. Конкретный процесс показан в следующем процессе. Реализация расширения HashMap в нормальных условиях показана на следующем рисунке:image.pngУзлы старого HashMap будут перенесены в новый HashMap по очереди, порядок передачи старого HashMap — A, B, C, в то время как новый HashMap использует метод вставки заголовка, поэтому окончательный порядок в новом HashMap — C , B, A, а также Так же, как показано на картинке выше. С этими предварительными условиями давайте посмотрим, как рождается бесконечный цикл?

1.1 Первый процесс выполнения бесконечного цикла

Бесконечный цикл вызван параллельным расширением HashMap. На первом этапе одновременного расширения поток T1 и поток T2 должны расширить HashMap. В это время T1 и T2 указывают на элемент головного узла A связанного списка, а T1 и T2. Следующие узлы, а именно T1.next и T2.next, указывают на узел B, как показано на следующем рисунке:image.png

1.2 Процесс выполнения бесконечного цикла 2

Второй шаг бесконечного цикла состоит в том, что поток T2 переходит в спящий режим после того, как квант времени потока T2 израсходован, и поток T1 начинает выполнять операцию расширения.Поток T2 не пробуждается до тех пор, пока не завершится расширение потока T1. Сцена после расширения показана на следующем рисунке:image.pngИз рисунка выше видно, что после выполнения потока T1 порядок HashMap изменился из-за метода вставки заголовка, но поток T2 не знает о том, что произошло, поэтому его указывающий элемент остается неизменным, как показано на рисунке выше. ., T2 указывает на элемент A, а T2.next указывает на элемент B.

1.3 Третий процесс выполнения бесконечного цикла

Когда поток T1 завершает выполнение, а поток T2 возобновляет выполнение, устанавливается бесконечный цикл, как показано на следующем рисунке:image.pngПоскольку следующим узлом узла B после того, как T1 завершил расширение, является A, и первым узлом, на который указывает поток T2, является A, а вторым узлом является B, этот порядок прямо противоположен порядку узлов после T1. полностью расширен.Порядок после выполнения T1 — от B до A, а порядок T2 — от A до B, так что узел A и узел B образуют бесконечный цикл., что является причиной бесконечного цикла HashMap.

1.4 Решения

Используйте потокобезопасные контейнеры вместо HashMap, такие как ConcurrentHashMap или Hashtable, поскольку производительность ConcurrentHashMap намного выше, чем у Hashtable, поэтому рекомендуется использовать ConcurrentHashMap вместо HashMap.

2. Проблема покрытия данных

Проблема с покрытием данных возникает в сценарии одновременного добавления элементов, не только в версии JDK 1.7, но и в других версиях, процесс формирования покрытия данных выглядит следующим образом:

  1. Когда поток T1 добавляет, он определяет, что элемент может быть вставлен в определенную позицию, но операция вставки не была выполнена, и его собственный квант времени израсходован.
  2. Поток T2 также выполняет операцию добавления, и хеш-значение, сгенерированное T2, такое же, как и у T1, то есть место, где будет храниться T2, такое же, как и у T1, потому что значение не было вставлено в этот location (поток T1 выполнил половину), поэтому T2 помещает свое значение. Значение сохраняется в текущем местоположении.
  3. После того, как T1 возобновляет выполнение, поскольку непустое суждение было выполнено, он не воспринимает, что в этой позиции уже есть значение, поэтому он вставляет свое собственное значение в эту позицию, затем значение T2 перезаписывается.

Конкретный поток выполнения показан на следующем рисунке.

2.1 Процесс выполнения покрытия данных 1

Поток T1 готов вставить данные k1:v1 в Null, но на самом деле он еще не выполнен, его собственный квант времени израсходован, и он переходит в спящее состояние, как показано на следующем рисунке:image.png

2.2 Процесс выполнения покрытия данных 2

Поток T2 готов вставить данные k2:v2 в Null, потому что здесь нет значения, если есть значение, он будет использовать цепной метод для вставки данных в следующую позицию без значения, но после оценки. обнаружили, что здесь нет значения, то данные вставляются напрямую, как показано на следующем рисунке:image.png

2.3 Третий процесс выполнения покрытия данных

После завершения выполнения потока T2 поток T1 возобновляет выполнение. Поскольку поток T1 определил, что эта позиция ранее не имела значения, она будет вставлена ​​напрямую. В это время значение, вставленное потоком T2, будет перезаписано, как показано на следующий рисунок:image.png

2.4 Решения

Решение такое же, как и первое решение, использование ConcurrentHashMap вместо HashMap может решить эту проблему.

3. Проблема беспорядка

здесьПроблема беспорядка относится к непоследовательному порядку, в котором HashMap добавляется и запрашивается., результат выполнения программы не соответствует ожидаемому программистом, как показано в следующем коде:

HashMap<String, String> map = new HashMap<>();
// 添加元素
for (int i = 1; i <= 5; i++) {
    map.put("2022-10-" + i, "Hello,Java:" + i);
}
// 查询元素
map.forEach((k, v) -> {
    System.out.println(k + ":" + v);
});

Порядок, который мы добавляем:image.pngМы ожидаем, что порядок запроса будет таким же, как и порядок добавления, но вывод приведенного выше кода таков:image.pngРезультаты выполнения не соответствуют нашим ожидаемым результатам, что является беспорядком HashMap. Мы ожидаем, что на выходе будет Hello, Java 1, 2, 3, 4, 5, но получаем следующий порядок: 2, 1, 4, 3, 5.

решение

Чтобы устранить беспорядок HashMap, нам нужно всего лишь заменить HashMap на LinkedHashMap, как показано в следующем коде:

LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 添加元素
for (int i = 1; i <= 5; i++) {
    map.put("2022-10-" + i, "Hello,Java:" + i);
}
// 查询元素
map.forEach((k, v) -> {
    System.out.println(k + ":" + v);
});

Результат выполнения вышеуказанной программы показан на следующем рисунке:image.png

Суммировать

В этой статье демонстрируются три классические проблемы HashMap, в которых возникают бесконечные циклы и покрытие данных при одновременном добавлении элементов, а проблема беспорядка — несоответствие между порядком добавления элементов и порядком запросов.Эти проблемы по сути являются всеми проблемами HashMap. Проблемы, вызванные неправильным использованием, такие как ConcurrentHashMap, следует использовать в случае многопоточности, LinkedHashMap следует использовать для обеспечения согласованности порядка вставки и порядка запросов, но мы не знакомы с HashMap в начале, поэтому эти проблемы Но как только вы их поймете, вы сможете лучше их использовать и лучше справляться с интервью. ​

Самостоятельно судить о правильном и неправильном, слушать других и подсчитывать выгоды и потери.

Официальная учетная запись: Анализ вопросов Java-интервью

Сборник статей:git ee.com/oppo/inter V…