Являются ли конечные статические переменные потоками безопасными в Java?
Я читал довольно много, но не нашел окончательного ответа.
У меня есть класс, который выглядит так:
public class Foo() {
private static final HashMap<String, HashMap> sharedData;
private final HashMap myRefOfInnerHashMap;
static {
// time-consuming initialization of sharedData
final HashMap<String, String> innerMap = new HashMap<String, String>;
innerMap.put...
innerMap.put...
...a
sharedData.put(someKey, java.util.Collections.unmodifiableMap(innerMap));
}
public Foo(String key) {
this.myRefOfInnerHashMap = sharedData.get(key);
}
public void doSomethingUseful() {
// iterate over copy
for (Map.Entry<String, String> entry : this.myRefOfInnerHashMap.entrySet()) {
...
}
}
}
И мне интересно, является ли потокобезопасным доступ к sharedData из экземпляров Foo (как показано в конструкторе и в doSomethingUseful()). Многие экземпляры Foo будут созданы в многопоточной среде.
Мое намерение состоит в том, что sharedData инициализируется в статическом инициализаторе и не изменяется после этого (только для чтения).
Я читал, что неизменяемые объекты по сути являются потокобезопасными. Но я видел это только в контексте переменных экземпляра. Являются ли неизменные статические переменные потоками безопасными?
Другая найденная мной конструкция была ConcurrentHashMap. Я мог бы сделать sharedData типа ConcurrentHashMap, но HashMaps, который он содержит, также должен быть типа ConcurrentHashMap? В основном..
private static final ConcurrentHashMap<String, HashMap> sharedData;
или
private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;
Или это было бы безопаснее (но более дорогостоящим просто клонировать())?
this.myCopyOfData = sharedData.get(key).clone();
ТИА.
(Статический инициализатор был отредактирован, чтобы создать больше контекста.)
Ответы
Ответ 1
ссылка на sharedData
, которая является окончательной, является потокобезопасной, поскольку она никогда не может быть изменена. Содержимое карты НЕ потокобезопасно, потому что она должна быть завершена предпочтительно с реализацией Guava ImmutableMap
или java.util.Collections.unmodifiableMap()
или использовать одну из реализаций Map в пакете java.util.concurrent
.
Только если вы выполняете ОБА, у вас будет полная безопасность потоков на карте. Любые содержащиеся в нем Карты должны быть неизменными или одной из параллельных реализаций.
.clone() в корне нарушена, избегайте
клонирование по умолчанию - это неглубокий клон, он просто вернет ссылки на объекты контейнера, а не полные копии. Он хорошо документирован в общедоступной информации о том, почему.
Ответ 2
Инициализация статических конечных полей в статическом блоке инициализации является потокобезопасной. Однако помните, что объект, к которому статические конечные контрольные точки не могут быть потокобезопасными. Если объект, к которому вы ссылаетесь, является потокобезопасным (например, неизменным), вы находитесь в явном виде.
Каждая отдельная HashMap, содержащаяся в вашем внешнем HashMap, не гарантируется потокобезопасностью, если вы не используете ConcurrentHashMap, как было предложено в вашем вопросе. Если вы не используете встроенную поточную реализацию HashMap, вы можете получить непреднамеренные результаты, когда два потока обращаются к одному и тому же внутреннему HashMap. Имейте в виду, что только некоторые операции с ConcurrentHashMap синхронизированы. Например, итерация не является потокобезопасной.
Ответ 3
Что такое потокобезопасность? Несомненно, инициализация HashMap является потокобезопасной в том отношении, что все Foo используют один и тот же экземпляр Map и что Карта, как гарантируется, будет там, если в статическом init не возникает исключение.
Но изменение содержимого Карты, безусловно, не является потокобезопасным. Статический финал означает, что Map sharedData нельзя переключить на другую карту. Но содержание Карты - это другой вопрос. Если данный ключ используется более одного раза в то же время, вы можете получить проблемы concurrency.
Ответ 4
Нет. За исключением случаев, когда они являются неизменными.
Единственное, что они делают, это
- Доступный уровень класса
- Избегайте ссылки, которую нужно изменить.
Тем не менее, если ваш атрибут изменен, он не является потокобезопасным.
См. также: Мы синхронизируем конечные переменные, которые являются окончательными?
Это точно то же самое, за исключением уровня класса.
Ответ 5
Да, это тоже потокобезопасность. Все конечные члены вашего статического класса будут инициализированы до того, как любой поток получит доступ к ним.
Если блок static
не работает во время инициализации, в потоке, который сначала пытается инициализировать, будет поднят ExceptionInInitializerError
. Последующая попытка ссылки на класс повысит значение NoClassDefFoundError
.
В общем, содержимое a HashMap
не гарантирует видимости по потокам. Однако в коде инициализации класса используется блок synchronized
для предотвращения инициализации нескольких потоков. Эта синхронизация будет очищать состояние карты (и экземпляры HashMap
, которые она содержит), чтобы они были правильно видимы для всех потоков — предполагая, что никаких изменений в карте или картах, которые она содержит, вне класса инициализатор.
Обратитесь к Language Language Specification, & sect; 12.4.2 для получения информации о инициализации класса и необходимости синхронизации.
Ответ 6
По сути нет ничего безопасного в отношении переменной final static
. Объявление переменной-члена final static
только гарантирует, что эта переменная назначается только один раз.
Вопрос о безопасности потоков имеет меньше общего с тем, как вы объявляете переменные, но вместо этого полагаетесь на то, как вы взаимодействуете с переменными. Таким образом, вы не можете ответить на свой вопрос без дополнительной информации о своей программе:
- Влияет ли несколько потоков на состояние вашей переменной
sharedData
?
- Если да, то выполняете ли вы синхронизацию всех записей (и чтения) из
sharedData
?
Использование ConcurrentHashMap гарантирует, что отдельные методы Map
являются потокобезопасными, он не выполняет такую операцию, как эта поточно-безопасная:
if (!map.containsKey("foo")) {
map.put("foo", bar);
}
Ответ 7
Разве вы не спрашиваете, является ли статическая инициализация sharedData
потокобезопасной и выполняется только один раз?
И да, это так.
Конечно, многие люди правильно указали, что содержимое sharedData
может быть изменено.
Ответ 8
В этом случае только объект sharedData является immmutable, это означает, что все время вы будете работать с одним и тем же объектом. Но любые данные внутри него могут быть изменены (удалены, добавлены и т.д.) В любой момент из любого потока.