1. Цели обучения
1. Причины небезопасности потока HashMap:
причина:
- В JDK1.7 функция HashMap#transfer() вызывается из-за многопоточного расширения HashMap. Конкретная причина: поток приостанавливается во время процесса выполнения, другие потоки завершили миграцию данных и приостанавливаются после того, как ресурсы ЦП выпущен Поток повторно выполняет предыдущую логику, и данные были изменены, что приводит к бесконечному циклу и потере данных.
- В JDK1.8 HashMap#putVal() вызывается из-за многопоточной операции размещения в HashMap Конкретная причина: предположим, что оба потока A и B выполняют операции размещения, а индекс вставки, вычисленный хеш-функцией, является то же самое, когда поток A завершает выполнение шестой строки кода, он приостанавливается из-за исчерпания кванта времени, а поток B вставляет элемент в нижний индекс после получения кванта времени, завершая обычную вставку, а затем поток A получает квант времени, так как оценка коллизии хэшей была выполнена ранее, все оценки не будут сделаны в это время, а вставка будет выполнена напрямую, что приведет к тому, что данные, вставленные потоком B, будут перезаписаны потоком A. , поэтому поток небезопасен.
улучшать:
- Потеря данных и бесконечный цикл были хорошо решены в JDK 1.8. Если вы прочитаете исходный код 1.8, вы обнаружите, что HashMap#transfer() не может быть найден, потому что JDK1.8 находится непосредственно в HashMap#resize() Миграция данных завершено.
2. Воплощение небезопасности потока HashMap:
- Небезопасность потока JDK1.7 HashMap отражается в: бесконечный цикл, потеря данных
- Небезопасность потока JDK1.8 HashMap отражается в: покрытии данных
2. Причины небезопасности потока HashMap, бесконечного цикла, потери данных и покрытия данных
1. Небезопасность потока, вызванная расширением JDK1.7.
Небезопасность потока HashMap в основном возникает в функции расширения, которая вызывает JDK1.7 HshMap#transfer():
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, перемещает нижний индекс каждого сегмента и использует метод вставки заголовка для переноса элементов в новый массив. Метод вставки заголовка изменит порядок связанного списка, что также является ключевым моментом для формирования бесконечного цикла. После понимания метода вставки головы продолжайте видеть, как вызвать бесконечный цикл и потерю данных.
2. Расширение вызывает бесконечный цикл и потерю данных
Предположим, что есть два потока A и B, одновременно расширяющие емкость следующего HashMap:
Результат после нормального расширения выглядит следующим образом:
Но когда поток A выполняет строку 11 передаточной функции выше, квант времени процессора исчерпывается, и поток A приостанавливается. То есть, как показано на следующем рисунке:
На данный момент в потоке A: e=3, next=7, e.next=null
Когда квант времени потока A исчерпан, ЦП начинает выполнять поток B и успешно завершает миграцию данных в потоке B.
Дело в том, что согласно модели памяти Java, после того, как поток B выполнит миграцию данных, и newTable, и таблица в основной памяти будут обновлены, то есть: 7.next=3, 3.next=null.
Затем поток A получает квант времени процессора и продолжает выполнять newTable[i] = e, а в соответствующую позицию нового массива помещает 3. После выполнения этого раунда цикла ситуация с потоком A выглядит следующим образом:
Затем продолжить выполнение следующего цикла.В это время e=7.При чтении e.next из основной памяти обнаруживается, что в основной памяти 7.next=3.В это время next=3, а 7 помещается в метод вставки головы в новый массив и продолжает выполнять этот раунд циклов, результаты следующие:
На данный момент нет проблем.
Последний раунд next=3, e=3, можно найти следующий цикл, 3.next=null, поэтому этот раунд циклов будет последним.
Затем, когда выполняется e.next=newTable[i], то есть 3.next=7, 3 и 7 соединяются друг с другом, когда выполняется newTable[i]=e, 3 повторно вставляется путем вставки заголовка В связанном списке результат выполнения показан на следующем рисунке:
Как упоминалось выше, e.next=null означает next=null, когда выполняется e=null, следующий цикл выполняться не будет. К моменту завершения операций раскрытия потоков A и B очевидно, что после выполнения потока A в HashMap появится кольцевая структура, а при дальнейшем использовании HashMap появится бесконечный цикл.
И из приведенного выше рисунка видно, что элемент 5 был необъяснимо потерян во время расширения, что вызвало проблему потери данных.
3. Независимо от небезопасности в JDK1.8
Потеря данных и бесконечный цикл, вызванные вышеуказанным расширением, были хорошо решены в JDK 1.8.Если вы прочитаете исходный код 1.8, вы обнаружите, что HashMap#transfer() не может быть найден, потому что JDK1.8 находится непосредственно в HashMap. Миграция данных выполняется в #resize().
зачем говоритьБудет ли JDK1.8 иметь покрытие данных?Давайте взглянем на следующий код операции put в JDK1.8:
Шестая строка кода предназначена для определения наличия коллизии хэшей.Предполагая, что оба потока A и B выполняют операции размещения, а нижний индекс вставки, вычисленный хэш-функцией, одинаков, когда поток A завершает выполнение шестой строки кода. , из-за времени После того, как срез исчерпан, он приостанавливается, и поток B вставляет элемент в нижний индекс после получения среза времени, завершая обычную вставку, а затем поток A получает срез времени. столкновение было выполнено ранее, все в это время больше не принимается решение, но вставка выполняется напрямую, что приводит к тому, что данные, вставленные потоком B, перезаписываются потоком A, поэтому поток небезопасен.
Вдобавок к этому в строке 38 кода указан размер ++. Мы так думаем, что это по-прежнему потоки A и B. Когда эти два потока одновременно выполняют операции ввода, при условии, что размер zise текущий HashMap равен 10, когда поток A выполняет 38-ю строку кода, он получает значение размера 10 из основной памяти и готовится выполнить операцию +1, но поскольку квант времени исчерпан, он должен отказаться Поток B радостно получает процессор или берет его из основной памяти. До значения size 10 выполняет операцию +1, завершает операцию put и записывает size=11 обратно в основную память, затем поток A снова получает процессор и продолжает выполняться (значение размера в это время все еще равно 10), когда операция размещения завершена После этого размер = 11 все еще записывается обратно в память.В это время оба потока A и B выполняют операцию размещения операции, но значение size увеличивается только на 1, поэтому говорят, что поток небезопасен из-за перезаписи данных.
3. Как заставить HashMap выполнять потокобезопасные операции в случае многопоточности?
Используйте Collections.synchronizedMap(map), чтобы обернуть его в синхронизированную карту.Принцип заключается в синхронизации всех методов HashMap.
Например: Collections.SynchronizedMap#get()
public V get(Object key) {
synchronized (mutex) {
return m.get(key);
}
}
4. Резюме
1. Причины небезопасности потока HashMap:
причина:
- В JDK1.7 функция HashMap#transfer() вызывается из-за многопоточного расширения HashMap. Конкретная причина: поток приостанавливается во время процесса выполнения, другие потоки завершили миграцию данных и приостанавливаются после того, как ресурсы ЦП выпущен Поток повторно выполняет предыдущую логику, и данные были изменены, что приводит к бесконечному циклу и потере данных.
- В JDK1.8 HashMap#putVal() вызывается из-за многопоточной операции размещения в HashMap Конкретная причина: предположим, что оба потока A и B выполняют операции размещения, а индекс вставки, вычисленный хеш-функцией, является то же самое, когда поток A завершает выполнение шестой строки кода, он приостанавливается из-за исчерпания кванта времени, а поток B вставляет элемент в нижний индекс после получения кванта времени, завершая обычную вставку, а затем поток A получает квант времени, так как оценка коллизии хэшей была выполнена ранее, все оценки не будут сделаны в это время, а вставка будет выполнена напрямую, что приведет к тому, что данные, вставленные потоком B, будут перезаписаны потоком A. , поэтому поток небезопасен.
улучшать:
- Потеря данных и бесконечный цикл были хорошо решены в JDK 1.8. Если вы прочитаете исходный код 1.8, вы обнаружите, что HashMap#transfer() не может быть найден, потому что JDK1.8 находится непосредственно в HashMap#resize() Миграция данных завершено.
2. Воплощение небезопасности потока HashMap:
- Небезопасность потока JDK1.7 HashMap отражается в: бесконечный цикл, потеря данных
- Небезопасность потока JDK1.8 HashMap отражается в: покрытии данных
5. Ссылка
blog.CSDN.net/Смотреть Fort_ocean/… классная оболочка.cai/articles/96…