У меня есть этот класс, в котором я кэширую экземпляры и клонирую их (данные изменяются), когда я их использую.
Интересно, могу ли я столкнуться с проблемой повторного заказа с этим.
Я просмотрел этот ответ и 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
из карты и видит нулевое поле 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
}
...
}
Это все еще склонно к той же проблеме?
Обратите внимание, что я не возражаю, если будет создан один или два дополнительных экземпляра, если несколько потоков попытаются создать и поместить экземпляр для одного и того же значения одновременно.
РЕДАКТИРОВАТЬ:
Что, если бы поля имени и данных были окончательными или изменчивыми?
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
}
...
}
Это все еще небезопасно? Насколько я понимаю, гарантии безопасности инициализации конструктора применяются только в том случае, если ссылка не сбежала во время инициализации. Я ищу официальные источники, подтверждающие это.
map.put
из конструктора. Но я не уверен, поэтому жду авторитетного ответа. - person lexicore   schedule 05.10.2017