У меня есть этот класс, где я кеширую экземпляры и клонирую их (данные изменяемы), когда я их использую.
Интересно, могу ли я столкнуться с проблемой переупорядочения с этим.
Я посмотрел этот ответ и JLS, но я все еще не уверен.
public class DataWrapper {
private static final ConcurrentMap<String, DataWrapper> map = new ConcurrentHashMap<>();
private Data data;
private String name;
public static DataWrapper getInstance(String name) {
DataWrapper instance = map.get(name);
if (instance == null) {
instance = new DataWrapper(name);
}
return instance.cloneInstance();
}
private DataWrapper(String name) {
this.name = name;
this.data = loadData(name); // A heavy method
map.put(name, this); // I know
}
private DataWrapper cloneInstance() {
return new DataWrapper(this);
}
private DataWrapper(DataWrapper that) {
this.name = that.name;
this.data = that.data.cloneInstance();
}
}
Мое мышление: Среда выполнения может изменить порядок оператора в конструкторе и опубликовать текущий экземпляр DataWrapper
(помещенный на карту) перед инициализацией объекта data
. Второй поток читает экземпляр DataWrapper
из карты и видит поле null data
(или частично построен).
Возможно ли это? Если да, то только из-за обращения ссылки?
Если нет, не могли бы вы объяснить мне, как рассуждать о происшествии - до согласованности в более простых терминах?
Что делать, если я сделал это:
public class DataWrapper {
...
public static DataWrapper getInstance(String name) {
DataWrapper instance = map.get(name);
if (instance == null) {
instance = new DataWrapper(name);
map.put(name, instance);
}
return instance.cloneInstance();
}
private DataWrapper(String name) {
this.name = name;
this.data = loadData(name); // A heavy method
}
...
}
Разве это все еще подвержено той же проблеме?
Обратите внимание, что я не против, если есть один или два дополнительных экземпляра, созданных, если несколько потоков пытаются создать и поместить экземпляр для одного и того же значения одновременно.
EDIT:
Что делать, если поля имени и данных были окончательными или изменчивыми?
public class DataWrapper {
private static final ConcurrentMap<String, DataWrapper> map = new ConcurrentHashMap<>();
private final Data data;
private final String name;
...
private DataWrapper(String name) {
this.name = name;
this.data = loadData(name); // A heavy method
map.put(name, this); // I know
}
...
}
Это все еще небезопасно? Насколько я понимаю, гарантии безопасности инициализации конструктора применимы только в том случае, если ссылка не сбежала во время инициализации. Я ищу официальные источники, подтверждающие это.