В Java безопасно менять ссылку на чтение HashMap одновременно

Надеюсь, это не слишком глупо вопрос...

В моем проекте есть код, похожий на следующий:

public class ConfigStore {

    public static class Config {

        public final String setting1;
        public final String setting2;
        public final String setting3;

        public Config(String setting1, String setting2, String setting3) {
            this.setting1 = setting1;
            this.setting2 = setting2;
            this.setting3 = setting3;
        }

    }

    private volatile HashMap<String, Config> store = new HashMap<String, Config>();

    public void swapConfigs(HashMap<String, Config> newConfigs) {
        this.store = newConfigs;
    }

    public Config getConfig(String name) {
        return this.store.get(name);
    }

}

Когда запросы обрабатываются, каждый поток запрашивает конфигурацию, которая будет использоваться из хранилища, используя функцию getConfig(). Однако периодически (каждые несколько дней, скорее всего) конфигурации обновляются и заменяются с помощью функции swapConfigs(). Код, который вызывает swapConfigs(), не поддерживает ссылку на карту, в которую он проходит, поскольку это просто результат анализа файла конфигурации.

  • В этом случае ключевое слово volatile все еще необходимо для переменной экземпляра хранилища?
  • Будет ли ключевое слово volatile вводить какие-либо потенциальные узкие места в производительности, о которых я должен знать или могу избежать, учитывая, что скорость чтения значительно превышает скорость записи?

Большое спасибо,

Ответ 1

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

Update

Как отметил @BeeOnRope в комментарии ниже, существует еще более серьезная причина использовать volatile:

"нелетучие записи [...] не устанавливают связь между событиями записи и последующего чтения, которые видят записанное значение, это означает, что поток может видеть новую карту, опубликованную через переменную экземпляра, но эта новая карта еще не построена полностью. Это неинтуитивно, но это следствие модели памяти, и это происходит в реальном слове. Чтобы объект был безопасно опубликован, он должен быть записан в volatile, или используйте несколько других методов.

Поскольку вы меняете значение очень редко, я не думаю, что volatile приведет к заметной разнице в производительности. Но, во всяком случае, правильное поведение козырей поведения.

Ответ 2

Нет, это не является потокобезопасным без изменчивости, даже если вы не видите значения устаревших значений. Несмотря на то, что на самой карте нет записей, а назначение ссылок является атомарным, новый Map<> не был опубликован безопасно.

Для того, чтобы объект был безопасно опубликован, он должен быть передан другим потокам с использованием какого-либо механизма, который либо устанавливает взаимосвязи между конструкцией объекта, ссылочной публикацией и ссылкой, либо должен использовать несколько более узких методы, гарантированные для публикации:

  • Инициализация ссылки на объект из статического инициализатора.
  • Сохранение ссылки на это в конечном поле.

Ни один из этих двух способов публикации не применим к вам, так что вам понадобится волатильность, чтобы установить, что произошло раньше.

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

Более подробную информацию о безопасной публикации можно найти в JCIP (настоятельно рекомендуется) или .

Ответ 3

Ваш код в порядке. Вам нужно volatile, иначе ваш код будет на 100% потокобезопасным (обновление ссылки будет атомарным), однако изменение может быть не видимым для всех потоков. Это означает, что некоторые потоки по-прежнему будут видеть старое значение store.

Это означает, что volatile является обязательным в вашем примере. Вы можете рассмотреть AtomicReference, но это не даст вам ничего больше в вашем случае.

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

BTW Мне нравится класс Config, который неизменен, пожалуйста, также учитывайте непреложную реализацию Map на всякий случай.

Ответ 4

Будет ли работать для вас использовать ConcurrentHashMap и вместо того, чтобы обменивать всю конфигурацию обновлений на значения хэш-карты?