Является ли использование ConcurrentHashMap определенным потокобезопасным?

Java

предисловие

Почему Лао Ван кричал посреди ночи? Почему несколько строк кода привели к тому, что сервер взорвался? Почему безопасность потоков все еще остается проблемой? Смотрим сегодняшнее "В 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, освобождающий руки программистам

Если у тебя что-то получится, поставь лайк

Обратите внимание на «Программу обезьяны посреди ночи» и поделитесь лучшими галантерейными товарами