Как выполнять операции с сопоставленными объектами, безопасные для параллелизма?

Я прочитал все и наоборот об использовании Java ConcurrentHashMap. Я надеюсь, что мой вопрос поможет прояснить то, что выглядит простым (с актуальными ответами).

У меня есть такая карта:

ConcurrentHashMap<Integer, ClassA> map = new ConcurrentHashMap<Integer, ClassA>()

Я использую ConcurrentHashMap, чтобы сохранить и обеспечить безопасность потока операций. Мой ClassA имеет некоторые атрибуты Integer/String и коллекцию строк.

Теперь я хотел бы знать, как безопасно обновить мой сопоставленный объект. Если я хочу создать метод для обновления объекта с моей карты, добавив новую строку, у меня есть что-то вроде:

        synchronized(map)
        {
            Collection<String> strings = map.get(id).getStrings();
            if(!strings.contains(newString)) //strings can't be null
            {
                strings.add(newString);
            }
        }

Защищен ли этот код от одновременного чтения/записи? Можно ли это сделать по-другому, используя Java API?


person TBag    schedule 19.01.2015    source источник
comment
Возможно, в вашем примере есть ошибки. Прямо сейчас код внутри синхронизированного блока не имеет ничего общего с картой. Есть переменная с именем events, но я не знаю, что это такое, наверное, это ваша карта. Если это так, то вам действительно не нужно синхронизироваться на карте. Извлеките эту логику, поместите ее в ClassA, а затем выполните некоторую синхронизацию.   -  person bbankowski    schedule 20.01.2015
comment
Действительно, я обновил описание с правильным названием. Это действительно моя карта. Я надеялся синхронизировать карту, чтобы фактически заблокировать всю таблицу и предотвратить любые одновременные изменения моих сопоставленных объектов. Можете ли вы разработать некоторую синхронизацию?   -  person TBag    schedule 20.01.2015
comment
Простейшим способом было бы создать метод public void synchronized add(String newString) в вашем ClassA. Параллельные модификации экземпляров ClassA будут синхронизированы, и вам не нужно будет синхронизироваться на карте.   -  person bbankowski    schedule 20.01.2015
comment
@bbankowski Если сделать все операции безопасными, создав внешний слой синхронизации, то нет смысла использовать ConcurrentHashMap   -  person lbalazscs    schedule 20.01.2015
comment
@lbalazscs Я думаю, что они все еще могут быть, поскольку мы не видим здесь операций ввода/вывода. Конечно, хорошо бы увидеть весь класс, чтобы принять правильное решение.   -  person bbankowski    schedule 20.01.2015
comment
Спасибо всем за попытку ответить на мой вопрос. Я опубликовал возможный ответ на него, который происходит так же, как использование ConcurrentHashMap. Буду признателен за ваше мнение!   -  person TBag    schedule 20.01.2015


Ответы (2)


То, что у вас есть в вашем ответе, не является полностью потокобезопасным. Он заменяет значение ключа id независимо от того, каким было старое значение. Если это подходит для вашей реализации, то отлично, но replace(K key, V oldObj, V newObj) — это идеальный способ проверки и установки (CAS) для замены существующего значения в ConcurrentMap.

В вашем конкретном случае использования вы должны сделать следующее

ClassA oldObj = map.get(id);
ClassA newObject = new ClassA(oldObject, newValue);
return map.replace(id, oldObj, newObject);

Это гарантирует, что карта будет обновлена ​​только в том случае, если предыдущее значение было oldObj. Что произойдет, если этот блок кода будет вызываться разными потоками с разными newValue?. Приведенный выше код позволит только одному потоку выполниться успешно, а другой поток вернет false.

person Nagesh Susarla    schedule 22.01.2015
comment
И если я хочу предотвратить гонку потоков между получением и заменой, должен ли я синхронизироваться на самой карте? - person TBag; 26.01.2015
comment
Вам не нужно. Максимум 1 вызов замены будет успешным, если есть гонка. Во второй раз замена вернет false. Если вы хотите, чтобы оба преуспели, вам придется поместить его в цикл do/while. - person Nagesh Susarla; 27.01.2015

Это попытка ответить на мой собственный вопрос. Это очень вдохновило меня на ответ на другой вопрос, который я нашел: Каков предпочтительный способ изменения значения в ConcurrentHashMap?

Если я сделаю свой ClassA неизменяемым и заменю свой код для обновления объектов моей карты таким образом:

        ClassA oldObject = map.get(id);
        ClassA newObject = new ClassA(oldObject, newValue);//this constructor copies the old one and add the newValue in my Collection of Strings
        map.put(id, newEvent);

Будет ли это потокобезопасно? Я действительно хочу, чтобы мой код был чистым и эффективным, используя ConcurrentHashMap, и я считаю, что это решение идет в том же направлении. Я просто немного подозрительно отношусь к любому потоку, извлекающему один и тот же объект, почему другой будет находиться между строками 2 и 3.

person TBag    schedule 20.01.2015