предисловие
Почему Лао Ван кричал посреди ночи? Почему несколько строк кода привели к тому, что сервер взорвался? Почему безопасность потоков все еще остается проблемой? Смотрим сегодняшнее "В IT"
текст
CurrentHashMap отображается фоном
Говоря об истории появления ConcurrentHashMap, мы должны начать с HashMap.
Лао Ванг — трудолюбивый разработчик Java в компании В интернет-индустрии бизнес всегда развивается очень быстро. Если это отражено в коде, то модуль v1.0 выполняется в один поток, на данный момент использование HashMap — хороший выбор. Однако в версии v1.5, ради производительности, Лао Ван посчитал, что было бы эффективнее изменить этот код на многопоточный, поэтому он изменил его, а затем с радостью выложил в сеть.
Пока однажды ночью я вдруг не получил онлайн-оповещение, а ЦП сервера был занят на 100%. В это время я проснулся и проверил (Baidu, Google) и обнаружил, что когда HashMap выполняет перехеширование в параллельной среде, это вызовет замкнутый цикл связанного списка, поэтому операция get() приводит к 100% загрузка процессора. О, оказывается, HashMap не является потокобезопасным классом, и в текущем бизнес-сценарии будут проблемы. Затем вы снова думаете о Hashtable в это время. Да, это потокобезопасный класс. Тогда я могу сначала заменить его этим классом. Больше никогда подобных проблем не возникало.
Но хорошие дни длились недолго, и коллеги из операции снова подошли к двери Фараон, почему функция ХХ такая медленная? В это время фараон задумался, не изменил ли я код? Разве вы не замените Hashtable в последний раз, есть ли здесь проблема с эффективностью? Затем еще одно расследование (Baidu, Google), я пошел, и, конечно же, выяснилось, что причина его потокобезопасности в том, что в метод добавлен synchronized, который заставляет сериализовать все наши операции, неудивительно, что он такой медленный .
После 2-кратного опыта ловушек, на этот раз Лао Ван был очень осторожен в поисках лучшего решения, в этот раз он нашел ConcurrentHashMap, и, чтобы снова не попасть в ловушку, также заранее пошел разбираться в принципе реализации. Оказалось, что этот класс использует сегментную блокировку сегмента, каждый сегмент имеет свою собственную блокировку, поэтому масштаб конфликта становится меньше, а эффективность можно значительно повысить. После расследования выяснилось, что это действительно хорошо, поэтому он уверенно заменил Hashtable.С тех пор операция никогда не жаловалась, и Лао Ван снова жил счастливой жизнью.
После периода интенсивного развития бизнеса проект на данный момент достиг версии 2.0.Предыдущий код, связанный с ConcurrentHashMap, был изменен до неузнаваемости, а логика стала намного сложнее, но проект был запущен вовремя и гладко . После того, как проект проработал какое-то время, снова возникла проблема безопасности потоков.
Почему возникла проблема?
Помимо сложного примера, давайте одновременно используем многопоточность, чтобы получить значение на карте, и добавим 1, чтобы увидеть, как выглядит окончательное выходное число.
public class CHMDemo {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String,Integer>();
map.put("key", 1);
ExecutorService executorService = Executors.newFixedThreadPool(100);
for (int i = 0; i < 1000; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
int key = map.get("key") + 1; //step 1
map.put("key", key);//step 2
}
});
}
Thread.sleep(3000); //模拟等待执行结束
System.out.println("------" + map.get("key") + "------");
executorService.shutdown();
}
}
На этом этапе мы смотрим на результаты нескольких выходов выполнения.
------790------
------825------
------875------
Наблюдая за выходными результатами, можно обнаружить, что этот код, использующий ConcurrentHashMap, имеет проблему с безопасностью потоков. Давайте проанализируем, почему это происходит. На шаге 1 и шаге 2 просто вызывается метод вызова ConcurrentHashMap, каждая из которых является атомарной операцией и является потокобезопасной. Но при их объединении будут проблемы.После того как поток A входит в метод, он получает значение ключа через map.get("key"). B также входит, так что это приводит к тому, что поток A и поток B получают значение ключа. тот же ключ. не только в
ConcurrentHashMap, то же самое относится и к другим потокобезопасным контейнерам, таким как Vector, поэтому вы не должны быть небрежными при использовании этих контейнеров.
Как решить?
1. Вы можете использовать синхронизированный
synchronized(this){
//step1
//step2
}
Но если мы используем этот метод, мы должны рассмотреть вопрос эффективности, сильно ли это повлияет на текущий бизнес?
2. Используйте атомарные классы
public class CHMDemo {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<String,AtomicInteger>();
AtomicInteger integer = new AtomicInteger(1);
map.put("key", integer);
ExecutorService executorService = Executors.newFixedThreadPool(100);
for (int i = 0; i < 1000; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
map.get("key").incrementAndGet();
}
});
}
Thread.sleep(3000); //模拟等待执行结束
System.out.println("------" + map.get("key") + "------");
executorService.shutdown();
}
}
------1001------
В это время выходной результат правильный, а эффективность намного выше, чем у первого решения.
Эпилог
Жизнь полна ловушек, как и написание кода.Думайте больше и уделяйте больше внимания.
Рекомендуемое чтение
"Народный язык понимает, что такое синхронный/асинхронный/блокирующий/неблокирующий》
"Лучшие практики обработки исключений Java и предотвращение ошибок》
"О нескольких позах и способах самоспасения при взрыве JVM》
"Supervisor, освобождающий руки программистам》
Обратите внимание на «Программу обезьяны посреди ночи» и поделитесь лучшими галантерейными товарами