Безопасно ли в 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
Ключевое слово представляет какие-либо потенциальные узкие места производительности, о которых я должен знать или которых можно избежать, учитывая, что скорость чтения значительно превышает скорость записи?
Спасибо большое,
4 ответа
Поскольку изменение ссылок является атомарной операцией, вы не получите один поток, модифицирующий ссылку, а другой увидит ссылку мусора, даже если вы отбросите volatile
, Однако для некоторых потоков новая карта может не отображаться мгновенно, что может привести к тому, что конфигурация чтения из старой карты будет сохраняться в течение неопределенного времени (или навсегда). Так держать volatile
,
Обновить
Как отметил @BeeOnRope в комментарии ниже, есть еще более веская причина для использования volatile
:
"энергонезависимые записи [...] не устанавливают отношения" происходит до " между записью и последующим чтением, которые видят записанное значение. Это означает, что поток может видеть новую карту, опубликованную через переменную экземпляра, но этот новый Карта еще не полностью построена. Это не интуитивно понятно, но это следствие модели памяти, и это происходит в реальном слове. Чтобы объект был опубликован безопасно, он должен быть записан в
volatile
или используйте несколько других методов.
Поскольку вы меняете значение очень редко, я не думаю, volatile
приведет к любой заметной разнице в производительности. Но, во всяком случае, правильное поведение влияет на производительность.
Нет, это не безопасно для потоков без изменчивых, даже если не считать проблем с устаревшими значениями. Несмотря на то, что нет никаких записей на саму карту, и назначение ссылки является атомарным, новый Map<>
не был безопасно опубликован.
Для того чтобы объект был безопасно опубликован, он должен быть передан другим потокам с использованием некоторого механизма, который либо устанавливает связь между событием до конструирования объекта, ссылочной публикацией и чтением ссылки, либо он должен использовать несколько более узких методов, которые гарантированно безопасен для публикации:
- Инициализация ссылки на объект из статического инициализатора.
- Сохранение ссылки на него в последнем поле.
Ни один из этих двух способов публикации не применим к вам, так что вам понадобится волатильность, чтобы установить, что происходит раньше.
Вот более длинная версия этого рассуждения, включая ссылки на JLS и некоторые примеры реальных вещей, которые могут произойти, если вы не будете безопасно публиковать.
Более подробную информацию о безопасной публикации можно найти в JCIP (настоятельно рекомендуется) или здесь.
Ваш код в порядке. Тебе нужно volatile
иначе ваш код будет на 100% поточно-ориентированным (обновление ссылки является атомарным), однако изменение может быть невидимым для всех потоков. Это означает, что некоторые потоки по-прежнему будут видеть старое значение store
,
Что, как говорится volatile
обязательно в вашем примере. Вы могли бы рассмотреть AtomicReference
, но это не даст вам больше ничего в вашем случае.
Вы не можете обменять правильность на производительность, поэтому ваш второй вопрос не является действительно действительным. Это окажет некоторое влияние на производительность, но, вероятно, только во время обновления, которое, как вы сказали, происходит очень редко. По сути, JVM обеспечит видимость изменения для всех потоков, "очистив" его, но после этого оно будет доступно как любая другая локальная переменная (вплоть до следующего обновления).
Кстати мне нравится Config
класс неизменяем, пожалуйста, также считайте неизменным Map
реализация на всякий случай.
Будет ли работать для вас использовать ConcurrentHashMap и вместо замены всей конфигурации обновить соответствующие значения в хэш-карте?