Обеспечение безопасной публикации и безопасности потоков в Java с помощью статических фабрик
Класс ниже должен быть неизменным (но см. Правку):
public final class Position extends Data {
double latitude;
double longitude;
String provider;
private Position() {}
private static enum LocationFields implements
Fields<Location, Position, List<Byte>> {
LAT {
@Override
public List<byte[]> getData(Location loc, final Position out) {
final double lat = loc.getLatitude();
out.latitude = lat;
// return an arrayList
}
@Override
public void parse(List<Byte> list, final Position pos)
throws ParserException {
try {
pos.latitude = listToDouble(list);
} catch (NumberFormatException e) {
throw new ParserException("Malformed file", e);
}
}
}/* , LONG, PROVIDER, TIME (field from Data superclass)*/;
}
// ========================================================================
// Static API (factories essentially)
// ========================================================================
public static Position saveData(Context ctx, Location data)
throws IOException {
final Position out = new Position();
final List<byte[]> listByteArrays = new ArrayList<byte[]>();
for (LocationFields bs : LocationFields.values()) {
listByteArrays.add(bs.getData(data, out).get(0));
}
Persist.saveData(ctx, FILE_PREFIX, listByteArrays);
return out;
}
public static List<Position> parse(File f) throws IOException,
ParserException {
List<EnumMap<LocationFields, List<Byte>>> entries;
// populate entries from f
final List<Position> data = new ArrayList<Position>();
for (EnumMap<LocationFields, List<Byte>> enumMap : entries) {
Position p = new Position();
for (LocationFields field : enumMap.keySet()) {
field.parse(enumMap.get(field), p);
}
data.add(p);
}
return data;
}
/**
* Constructs a Position instance from the given string. Complete copy
* paste just to get the picture
*/
public static Position fromString(String s) {
if (s == null || s.trim().equals("")) return null;
final Position p = new Position();
String[] split = s.split(N);
p.time = Long.valueOf(split[0]);
int i = 0;
p.longitude = Double.valueOf(split[++i].split(IS)[1].trim());
p.latitude = Double.valueOf(split[++i].split(IS)[1].trim());
p.provider = split[++i].split(IS)[1].trim();
return p;
}
}
Будучи неизменным, это также потокобезопасно и все такое. Как вы видите, единственный способ создать экземпляры этого класса - кроме рефлексии, которая на самом деле является другим вопросом - это использовать предоставленные статические фабрики.
Вопросы:
- Есть ли случай, когда объект этого класса может быть небезопасно опубликован?
- Есть ли случаи, когда возвращаемые объекты небезопасны?
РЕДАКТИРОВАТЬ: пожалуйста, не комментируйте поля, не являющиеся частными - я понимаю, что это не неизменный класс по словарю, но пакет находится под моим контролем, и я никогда не буду изменять значение поля вручную (после создания ofc), Мутаторы не предоставляются.
С другой стороны, поля, не являющиеся окончательными, - это суть вопроса. Конечно, я понимаю, что если бы они были окончательными, класс был бы действительно неизменным и потокобезопасным (по крайней мере, после Java5). Я был бы признателен за пример плохого использования в этом случае.
Наконец, я не хочу сказать, что фабрики, являющиеся статичными, имеют какое-либо отношение к безопасности потоков, как, по-видимому, подразумевают некоторые из комментариев. Важно то, что единственный способ создать экземпляры этого класса - через эти (конечно, статичные) фабрики.
3 ответа
Да, экземпляры этого класса могут быть опубликованы небезопасно. Этот класс не является неизменяемым, поэтому, если поток создания экземпляра делает экземпляр доступным для других потоков без барьера памяти, эти потоки могут видеть экземпляр в частично сконструированном или иным образом несовместимом состоянии.
Термин, который вы ищете, является по сути неизменным: поля экземпляра могут быть изменены после инициализации, но на самом деле это не так.
Такие объекты могут безопасно использоваться несколькими потоками, но все зависит от того, как другие потоки получают доступ к экземпляру (т. Е. Как они публикуются). Если вы поместите эти объекты в параллельную очередь для использования другим потоком - нет проблем. Если вы назначите их в поле, видимое для другого потока в синхронизированном блоке, и notify()
wait()
нить, которая их читает - нет проблем. Если вы создадите все экземпляры в одном потоке, который затем запустит новые потоки, которые их используют -не проблема!
Но если вы просто назначаете их в энергонезависимое поле, и когда-нибудь "позже" другой поток прочитает это поле, это проблема! И поток записи, и поток чтения нуждаются в точках синхронизации, чтобы можно было сказать, что запись действительно произошла до чтения.
Ваш код не публикуется, поэтому я не могу сказать, делаете ли вы это безопасно. Вы можете задать тот же вопрос об этом объекте:
class Option {
private boolean value;
Option(boolean value) { this.value = value; }
boolean get() { return value; }
}
Если вы делаете что-то "лишнее" в своем коде, которое, по вашему мнению, будет иметь значение для безопасной публикации ваших объектов, укажите это.
Положение не является неизменным, поля имеют видимость пакета и не являются окончательными, см. Определение неизменяемых классов здесь: http://www.javapractices.com/topic/TopicAction.do?Id=29.
Кроме того, позиция не публикуется безопасно, потому что поля не являются окончательными, и нет другого механизма для обеспечения безопасной публикации. Концепция безопасной публикации объясняется во многих местах, но она кажется особенно актуальной: http://www.ibm.com/developerworks/java/library/j-jtp0618/ Существуют также соответствующие источники по SO.
Короче говоря, безопасная публикация - это то, что происходит, когда вы передаете ссылку на созданный вами экземпляр другому потоку. Будет ли этот поток видеть значения полей, как предполагалось? ответ здесь - нет, потому что компилятор Java и JIT могут свободно переупорядочивать инициализацию поля со справочной публикацией, что приводит к тому, что наполовину запеченное состояние становится видимым для других потоков.
Этот последний пункт имеет решающее значение: от комментария ОП до одного из приведенных ниже ответов он, похоже, считает, что статические методы каким-то образом работают иначе, чем другие методы, но это не так. Статический метод может быть встроенным во многом как любой другой метод, и то же самое верно для конструкторов (исключение составляют заключительные поля в конструкторах после Java 1.5). Чтобы было ясно, хотя JMM не гарантирует безопасность конструкции, она может хорошо работать на некоторых или даже на всех JVM. Подробное обсуждение, примеры и мнения отраслевых экспертов можно найти в этом списке рассылки по интересам, связанным с параллелизмом: http://jsr166-concurrency.10961.n7.nabble.com/Volatile-stores-in-constructors-disallowed-to-see-the-default-value-td10275.html по умолчанию значение-td10275.html
Суть в том, что это может работать, но это не безопасная публикация в соответствии с JMM. Если вы не можете доказать, что это безопасно, это не так.
Поля Position
класс не final
, поэтому я считаю, что их значения не безопасно опубликованы конструктором. Поэтому конструктор не является поточно-ориентированным, поэтому никакой код (например, методы фабрики), который их использует, не создает поточно-ориентированные объекты.