Что такое "неполностью построенный объект"?
Goetz Java Concurrency in Practice, стр. 41, упоминает, как ссылка this
может уйти во время построения. Пример "не делайте этого":
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
Здесь this
"экранируется" из-за того, что doSomething(e)
относится к прилагаемому экземпляру ThisEscape
. Ситуация может быть исправлена с помощью статических методов factory (сначала создайте простой объект, а затем зарегистрируйте слушателя) вместо публичных конструкторов (выполняющих всю работу). Книга продолжается:
Публикация объекта из его конструктора может публиковать неполностью сконструированный объект. Это верно, даже если публикация является последним утверждением в конструкторе. Если во время построения избегается ссылка this
, объект считается неправильно сконструированным.
Я не совсем понимаю это. Если публикация является последним утверждением в конструкторе, не все ли было сделано до этого? Почему this
недействителен к тому времени? Видимо, после этого происходит несколько вуду, но что?
Ответы
Ответ 1
Конец конструктора является особым местом в терминах concurrency относительно окончательных полей. Из раздел 17.5 Спецификации языка Java:
Объект считается полностью инициализируется, когда конструирование конструктора. Поток, который может видеть только ссылку на объект после этого объекта был полностью при инициализации гарантировано правильно инициализированные значения для этого объектов.
Модель использования конечных полей - это простой один. Установите конечные поля для объект в объекте конструктор. Не пишите ссылку к объекту, построенному в место, где другой поток может видеть это перед конструктором объекта законченный. Если это следует, то когда объект рассматривается другим нить, этот поток всегда будет видеть правильно построенная версия это конечные поля объекта. Это будет также см. версии любого объекта или массив, на который ссылаются эти конечные поля которые, по крайней мере, настолько же актуальны, как и конечные поля.
Другими словами, ваш слушатель может увидеть конечные поля со значениями по умолчанию, если он исследует объект в другом потоке. Это не произойдет, если регистрация слушателя произошла после завершения конструктора.
С точки зрения того, что происходит, я подозреваю наличие скрытого барьера памяти в самом конце конструктора, следя за тем, чтобы все потоки "видели" новые данные; без использования этого барьера памяти могут возникнуть проблемы.
Ответ 2
Другая проблема возникает при подклассе ThisEscape, и дочерний класс вызывает этот конструктор. Неявная эта ссылка в EventListener имела бы неполностью сконструированный объект.
Ответ 3
Между окончанием registerListener и возвратом конструктора существует небольшое, но конечное время. В этот момент может использоваться другой поток, который можно использовать, и попытаться вызвать doSomething(). Если время выполнения не вернулось прямо к вашему коду в это время, объект может находиться в недопустимом состоянии.
Я не уверен в java, но один пример, о котором я могу думать, - это где, возможно, среда выполнения перемещает экземпляр, прежде чем возвращаться к вам.
Своя небольшая возможность я вам даю.