Можем ли мы добиться считывания измененных данных после записи атомарно в ConcurrentHashMap в Java? - программирование
Подтвердить что ты не робот

Можем ли мы добиться считывания измененных данных после записи атомарно в ConcurrentHashMap в Java?

Я пытаюсь найти ответ на них, но не могу понять (или подтвердить) его в Google или в документах Java.

Моя реализация выглядит следующим образом:

Map<String, POJO> map = new ConcurrentHashMap<String, POJO>();

если я сделаю

value1 = map.get(key1);
value1.setProp(prop);

Любой другой поток может переопределить.

Теперь, я думаю, если я буду делать следующее: это будет атомная операция /, другими словами, она заблокирует сегмент key1?

map.compute(key1, (key1, value1) -> { value1.setProp(prop) });

Javadoc для compute функции

Попытка вычислить сопоставление для указанного ключа и его текущего отображаемого значения (или null, если нет текущего сопоставления). Весь вызов метода выполняется атомарно. Некоторые попытки выполнить операции обновления на этой карте другими потоками могут быть заблокированы, пока выполняется вычисление, поэтому вычисление должно быть коротким и простым и не должно пытаться обновлять любые другие сопоставления этой Карты.

Рекомендации:

1. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html 2. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#compute-K-java.util.function.BiFunction-

РЕДАКТИРОВАТЬ:

для моей окончательной реализации я сделал что-то вроде этого, поскольку все потоки разделяют NewMap и в конце, я новый список POJO
Абстрактный тип данных

    public class NewMap {

        private Map<String, POJO> map;
        private boolean isUpdatable;
        void NewMap(){
              isUpdatable = true;
              map = new ConcurrentHashMap();
        }

        void putPOJOProp1(String key, Type value) throws ReadOnlyException{

                  map.compute(key, (k,v) -> {
                           if(!isUpdatable) throw new ReadOnlyException();
                           if(k == null){ 
                              POJO p = new POJO();
                              p.setProp1(value);
                              v = p;
                           } else { 
                              v = v.setProp1(v)
                           }
             });
        }

        void putPOJOProp2....
        void putPOJOProp3....

        List<POJO> getAll() {
             isUpdatable = false;
             List<POJO> pojos;
             for(key: map.getKeys()) {
                  Pojo p = map.get(key);
                  p.setKey(key);
                  pojos.add(p);
             }
             return pojos;
        }
    }
4b9b3361

Ответ 1

Вы смешиваете два разных термина.
ConcurrentHashMap предотвращает повреждение внутренней структуры, но не препятствует условиям гонки. Если все, что вы хотите избежать, это повреждение данных, то использование ConcurrentHashMap действительно.
Но из вашего вопроса кажется, что вы пытаетесь избежать условий гонки. ConcurrentHashMap никоим образом не защищает вас от них.

Чтобы лучше понять это, рассмотрим следующий пример:

    Map<String, POJO> map = new ConcurrentHashMap<>();
    ExecutorService pool = Executors.newWorkStealingPool(10);

    for (int t = 0; t < 10; t++) {
        pool.execute(() -> {
            for (int i = 0; i < 100_000; i++) {
                map.compute("A", (k, v) -> {
                    if (v == null) {
                        return new POJO();
                    }
                    v.a = ++v.a;
                    v.b = ++v.b;

                    return v;
                });
            }
        });
    }

    pool.awaitTermination(5, TimeUnit.SECONDS);

    System.out.println(map);

    // With POJO like 
    class POJO {
       // toString() here
       Integer a = 1;
       Integer b = 1;
    }

Здесь мы получаем {A=POJO{a=1000000, b=1000000}}, поэтому наша операция была потокобезопасной. Если все, что ты хочешь, получишь, ты в порядке.

Ответ 2

Состояние Javadoc для ConcurrentHashMap.compute

Весь вызов метода выполняется атомарно.

Обратите внимание, что сравнение ConcurrentSkipListMap.compute является атомарным.

Более компактная форма ответа Алексея Сошина

Map<String, long[]> map = new ConcurrentSkipListMap<>();
map.put("A", new long[2]);
IntStream.range(0, 1_000_000)
        .parallel()
        .forEach(i -> map.compute("A", (k, v) -> {
            v[0]++;
            v[1]++;
            return v;
        }));
System.out.println(Arrays.toString(map.get("A")));

печатает что-то вроде

[643553, 597254]

cf HashMap производит что-то вроде

[244786, 245993]

Однако используйте ConcurrentHashMap и вы получите ожидаемый

[1000000, 1000000]