Потокобезопасность HashMap в Java

233
22 июня 2022, 14:50

Встречал много статей, где предлагают как вполне возможный вариант использовать java.util.HashMap из разных потоков, при этом применив синхронизацию через synchronized или обернув через Collections.synchronizedMap(...).

Данная разновидность Map не имеет volative полей, следовательно, я не понимаю, в чем смысл использовать это из разных потоков через synchronized, если потоки не узнают о изменении Map, т.к. ее поля не volatile.

Пожалуйста, помогите прояснить момент!

Answer 1

Тема не простая, хотя казалось бы, давайте разберемся как это работает:

При указании volatile мы говорим что нужно записать значение сразу в кучу минуя локальный кэш потока. При этом заставляя JVM отказаться от оптимизации.

Вот пример как volatile может влиять на поведение потока (допустим у нас есть переменная int count):

  1. Я поток №1, выполняюсь тут себе.
  2. О, мне пришла задача изменить значение count на 9, сейчас сделаем.
  3. Так, volatile нет, меняем значение в локальном кэше, поменяли.
  4. Меняем значение в куче, стоп, поток №2 требует времени, ок я подожду.
  5. Я вернулся, продолжаем, меняю значение в куче на 9.

Если бы у нас было volatile int count, то поток №1 успел бы записать значение за одну операцию сразу в кучу. И тогда поток №2 прочитал бы уже измененное значение потоком №1.

Нужно отметить что, мы не выбираем когда и какой поток будет выполняться. Если JVM считает нужным отдать приоритет на выполнение другому потоку, она это сделает. Может конечно и получиться так что без volatile поток № 1 успеет записать значение, а может и нет. Для избежания неоднозначности можно использовать volatile.

Это все справедливо для примитивов. Для объектов volatile записывает сразу в кучу только ссылку. Допустим есть класс User и мы с ним работаем, получается что теперь и поля в User нужно пометить volatile, тогда рекурсивно и все поля объекты User должны быть volatile. Теряем всю оптимизацию...

Что же делать? Тут на помощь приходит synchronized. synchronized блок дает доступ только одному потоку, второй поток блокируется и ждет выхода первого.

Чем HashMap отличается от synchronizedMap?

    @Override
    public V put(K key, V value) {
        synchronized (monitor) {
            return provider.put(key, value);
        }
    }
    @Override
    public V get(Object key) {
        synchronized (monitor) {
            return provider.get(key);
        }
    }

В методы get() и put() добавлены блоки synchronized, как можно заметить синхронизация на одном объекте. Это значит что пока первый поток не выйдет из метода get()/put(), второй поток будет заблокирован и ждать своей очереди. Как только первый поток выйдет из метода get()/put(), тогда второй поток разблокируется и сможет войти в метод get()/put().

Какой вывод? Первый поток выполняет нужные ему действия получает или вставляет значение, затем приходит второй поток и выполняет свои действия с уже измененными данными (разумеется если выполнялся метод put()).

У synchronizedMap есть проблема, он блокирует доступ сразу и на чтение и на вставку одновременно, что снижает скорость.

Поэтому есть класс ConcurrentHashMap. У него чтение без блокировки, а вот вставлять можно только по очереди, что делает его быстрее synchronizedMap.

Какой вывод из этого всего: Зачем писать самому, если можно использовать уже готовые оптимизированные решения синхронизированных коллекций.

надеюсь я понятно объяснил)

READ ALSO
кастомный скролл для ie 11

кастомный скролл для ie 11

подскажите либу чтобы можно было сделать кастомный скролл для ie11, вид скроллбара снизу

264
Размер body. Как это работает?

Размер body. Как это работает?

Я хочу сделать множественный фон (вода и рыба)Рыбе хочу задать определенное местоположение, например background-position: right center

163