Ответ 1
Пример, который вы опубликовали в своем вопросе, получен из "Java Concurrency In Practice" от Brian Goetz et al. Это в разделе 3.2 "Публикация и побег". Я не буду пытаться воспроизвести детали этого раздела здесь. (Идите купите копию своей книжной полки или займите копию у своих сотрудников!)
Проблема, проиллюстрированная примером кода, заключается в том, что конструктор позволяет ссылаться на объект, который создается для "escape", прежде чем конструктор завершит создание объекта. Это проблема по двум причинам:
-
Если ссылка исчезает, что-то может использовать объект, прежде чем его конструктор завершит инициализацию и увидит его в непоследовательном (частично инициализированном) состоянии. Даже если объект выходит после завершения инициализации, объявление подкласса может привести к его нарушению.
-
Согласно JLS 17.5, окончательные атрибуты объекта можно безопасно использовать без синхронизации. Однако это справедливо только в том случае, если ссылка на объект не опубликована (не исчезает) до завершения его конструктора. Если вы нарушите это правило, результатом будет коварная ошибка Concurrency, которая может укусить вас, когда код будет выполнен на многоядерных/многопроцессорных машинах.
Пример ThisEscape
скрыт, потому что ссылка экранируется с помощью ссылки this
, переданной неявно на анонимный конструктор класса EventListener
. Однако те же проблемы возникнут, если ссылка будет опубликована слишком скоро.
Вот пример, иллюстрирующий проблему неполностью инициализированных объектов:
public class Thing {
public Thing (Leaker leaker) {
leaker.leak(this);
}
}
public class NamedThing extends Thing {
private String name;
public NamedThing (Leaker leaker, String name) {
super(leaker);
}
public String getName() {
return name;
}
}
Если метод Leaker.leak(...)
вызывает getName()
на пропущенном объекте, он получит null
... потому что в тот момент времени цепочка конструктора объекта не завершилась.
Вот пример, иллюстрирующий проблему небезопасной публикации для атрибутов final
.
public class Unsafe {
public final int foo = 42;
public Unsafe(Unsafe[] leak) {
leak[0] = this; // Unsafe publication
// Make the "window of vulnerability" large
for (long l = 0; l < /* very large */ ; l++) {
...
}
}
}
public class Main {
public static void main(String[] args) {
final Unsafe[] leak = new Unsafe[1];
new Thread(new Runnable() {
public void run() {
Thread.yield(); // (or sleep for a bit)
new Unsafe(leak);
}
}).start();
while (true) {
if (leak[0] != null) {
if (leak[0].foo == 42) {
System.err.println("OK");
} else {
System.err.println("OUCH!");
}
System.exit(0);
}
}
}
}
Некоторые прогоны этого приложения могут печатать "OUCH!" вместо "ОК", указывая, что основной поток заметил объект Unsafe
в "невозможном" состоянии из-за небезопасной публикации через массив leak
. Это произойдет или не будет зависеть от вашей JVM и вашей аппаратной платформы.
Теперь этот пример явно искусственен, но нетрудно представить, как это может происходить в реальных многопоточных приложениях.