Java: Как автоматически заменить все значения на карте? - PullRequest
0 голосов
/ 14 мая 2018

У меня есть компонент с состоянием в многопоточной среде, который сохраняет свое состояние на карте.Теперь мне нужен способ заменить все значения этой карты в одном атомарном действии.

public final class StatefulBean {

    private final Map<String, String> state = new ConcurrentSkipListMap<>();

    public StatefulBean() {
        //Initial state
        this.state.put("a", "a1");
        this.state.put("b", "b1");
        this.state.put("c", "c1");
    }

    public void updateState() {
        //Fake computation of new state
        final Map<String, String> newState = new HashMap<>();
        newState.put("b", "b1");
        newState.put("c", "c2");
        newState.put("d", "d1");

        atomicallyUpdateState(newState);
        /*Expected result
         *  a: removed
         *  b: unchanged
         *  C: replaced
         *  d: added*/
    }

    private void atomicallyUpdateState(final Map<String, String> newState) {
        //???
    }
}

В настоящее время я использую ConcurrentSkipListMap в качестве реализации ConcurrentMap, но это не является обязательным требованием.

Единственный способ решить эту проблему - сделать глобальный state volatile и полностью заменить карту или использовать AtomicReferenceFieldUpdater.Есть ли лучший способ?

Мои обновления довольно часты, один или два раза в секунду, но, скорее всего, очень мало значений.Также вся карта будет содержать только менее 20 значений.

Ответы [ 5 ]

0 голосов
/ 14 мая 2018

Подход с CAS и AtomicReference будет копировать содержимое карты при каждом массовом обновлении.

AtomicReference<Map<String, String>> workingMapRef = new AtomicReference<>(new HashMap<>());

Эта карта может быть одновременной, но для «массовых обновлений» она доступна только для чтения. Затем в updateState зацикливание doUpdateState(), пока вы не получите истину, а это означает, что ваши значения были обновлены.

void updateState() {
    while (!doUpdateState());
}

boolean doUpdateState() {
    Map<String, String> workingMap = workingMapRef.get();
    //copy map content
    Map<String, String> newState = new HashMap<>(workingMap); //you can make it concurrent

    newState.put("b", "b1");
    newState.put("c", "c2");
    newState.put("d", "d1");

    return workingMapRef.compareAndSet(workingMap, newState);
}
0 голосов
/ 14 мая 2018

Самый простой метод с наименьшим суетой - переключать карту вместо замены содержимого карты. Использование volatile или AtomicReference (я не понимаю, зачем вам нужно AtomicReferenceFieldUpdater, в частности), не должно иметь большого значения.

Это гарантирует, что ваша карта всегда в правильном состоянии, и позволяет вам также делать снимки. Это не защищает вас от других проблем параллелизма, поэтому, если что-то вроде потерянных обновлений является проблемой, вам понадобится дополнительный код (хотя AtomicReference даст вам CAS методов для их обработки).

Вопрос на самом деле довольно прост, если вы рассматриваете только полную атомную замену карты. Было бы полезно узнать, какие другие операции влияют на карту и как. Я также хотел бы услышать, почему ConcurrentSkipListMap был выбран вместо ConcurrentHashMap.

0 голосов
/ 14 мая 2018

Поскольку карта довольно маленькая, вероятно, достаточно просто использовать synchronized во всех местах, где вы к ней обращаетесь.

private void atomicallyUpdateState(final Map<String, String> newState) {
    synchronized(state) {
        state.clear();
        state.putAll(newState);
    }
}

, но не забывайте ни о каких, как и при любых других вещах вроде

String myStatevalue = state.get("myValue");

должно стать

String myStatevalue;
synchronized (state) {
    myStatevalue = state.get("myValue");
}

, иначе чтение и обновление не синхронизируются и вызывают состояние гонки.

0 голосов
/ 14 мая 2018

Поскольку клиентский код поддерживает ссылку на бин, а не на карту, замена значения (т. Е. Всей карты) может показаться самым простым решением.

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

Это то, как это сделал бы функциональный программист.

0 голосов
/ 14 мая 2018

Расширьте реализацию карты по вашему выбору и добавьте синхронизированный метод:

class MyReplaceMap<K, V> extends HashMap<K, V> //or whatever
{
    public synchronized void replaceKeys(final Map<K, V> newMap)
    {
        //.. do some stuff
    }
}

Конечно, вы всегда можете сделать state неконечным энергозависимым и переназначить его ( назначениеатомный )

private volatile Map<String, String> state = new HashMap<>();

//...

final Map<String, String> newState = new HashMap<>();
newState.put("b", "b1");
newState.put("c", "c2");
newState.put("d", "d1");
state = newState;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...