В Java безопасно ли изменять ссылку на HashMap, считываемую одновременно - PullRequest
12 голосов
/ 25 августа 2011

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

В моем проекте есть код, подобный следующему:

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 представлять потенциальные узкие места производительности, о которых мне следует знать или которых можно избежать, если учесть, что скорость чтения значительно превышает скорость записи?

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

Ответы [ 4 ]

11 голосов
/ 20 июня 2013

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

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

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

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

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

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

10 голосов
/ 25 августа 2011

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

Обновление

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

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

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

9 голосов
/ 25 августа 2011

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

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

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

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

1 голос
/ 25 августа 2011

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

...