Как обрабатывать параллельные обновления и чтения в POJO, хранящемся на карте
Я планирую реализовать простой кэш в памяти для поиска с использованием CHM, который работает не более чем с двумя параллельными потоками. Один поток выполняет итерацию и обновление CHM с использованием итератора, а второй поток читает значения из карты.
Как показывает мое понимание, и то, что я испытывал до сих пор, CHM Iterator является отказоустойчивым, что означало бы, что итерация произойдет на снимке данных.
Итак, давайте предположим, что Поток A извлекает значение из CHM, которое является POJO, используя ключ, и выполняет итерацию / обновление POJO. В то же время ThreadB получает тот же POJO. Так, каково было бы ожидаемое поведение в этой точке?
- Будет ли ThreadB видеть обновления, которые делает Thread A? Я думаю, нет, потому что Thread A все еще находится в процессе обновления.
- Если да, пожалуйста, поделитесь своими мыслями, как это произойдет?
- Если нет, предложите эффективные альтернативные способы, если вы их реализовали.
POJO что-то вроде ниже.
Class Pojo{
private volatile long a;
private volatile long b;
....
public long getA() {
return this.a;
}
void setA(long a) {
this.a = a;
}
}
3 ответа
Как показывает мое понимание, и то, что я испытывал до сих пор, CHM Iterator является отказоустойчивым, что означало бы, что итерация произойдет на снимке данных.
Это не правильно. Согласно документации:
Точно так же, Iterators, Spliterators и Enumerations возвращают элементы, отражающие состояние хеш-таблицы в некоторой точке во время или после создания итератора / перечисления.
[акцент мой]
Итак, давайте предположим, что Поток A извлекает значение из CHM, которое является POJO, используя ключ, и выполняет итерацию / обновление POJO. В то же время ThreadB получает тот же POJO. Так, каково было бы ожидаемое поведение в этой точке?
Если Поток A изменяет POJO, а Поток B проверяет тот же POJO, то ConcurrentHashMap вообще не имеет значения: не имеет большого значения, как два потока получили POJO, только то, как сам POJO обрабатывает параллельные обновления и чтения.
Вы ничего не сказали нам о классе POJO, но если он не был тщательно спроектирован, чтобы разрешать атомарные обновления и чтения, то более или менее очевидно, что поток B иногда будет просматривать POJO в несовместимом состоянии с некоторыми потоками. А читает, но не все из них.
Ну, ты не можешь сделать что-то подобное.
Pojo myPojo = new Pojo();
myPojo.setA(10);
myPojo.setB(11);
// Atomic replace
CHM.replace(key, myPojo);
Замена CHM является атомарной, и никакой другой поток не будет считать устаревшее значение POJO, пока выполняется замена.
- Значение вашего CHM должно быть AtomicReference
- Сделайте класс Pojo неизменным, обновление любого нового значения должно создать новый объект Pojo.
- Реализуйте конструктор, который принимает все параметры в конструкторе.
- Реализуйте каждый метод set, как пример, приведенный ниже
public Pojo setA(int newA ){ Pojo newPojo = new Pojo(newA, this.getB()); return newPojo; }
- При обновлении CHM следуйте инструкциям
AtomicReference pojoRef = CHM.get(Key); Pojo oldPojo = pojoRef.get(); Pojo newPojo = oldPojo.setA(10); while(!pojoRef.compareAndSet(oldPojo, newPojo) ){ AtomicReference pojoRef = CHM.get(Key); Pojo oldPojo = pojoRef.get(); Pojo newPojo = oldPojo.setA(10); }