Что такое «не полностью построенный объект»? - PullRequest
24 голосов
/ 25 марта 2010

Goetz's Java-параллелизм на практике , стр. 41, упоминает, как ссылка this может выходить из строя во время строительства. Пример "не делай этого":

public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
    }
}

Здесь this «ускользает» благодаря тому факту, что doSomething(e) относится к включающему экземпляру ThisEscape. Ситуацию можно исправить, используя статические фабричные методы (сначала создайте простой объект, затем зарегистрируйте слушателя) вместо открытых конструкторов (выполняющих всю работу). Книга продолжается:

Публикация объекта из его конструктора может опубликовать не полностью построенный объект. Это верно , даже если публикация является последним оператором в конструкторе. Если ссылка this избегает во время построения, объект считается неправильно сконструированным.

Я не совсем понял. Если публикация является последним утверждением в конструкторе, разве вся работа по конструированию не была сделана до этого? Почему к тому времени this не действует? Видимо, после этого происходит какое-то вуду, но что?

Ответы [ 3 ]

16 голосов
/ 25 марта 2010

Конец конструктора - это особое место с точки зрения параллелизма по отношению к конечным полям. Из раздела 17.5 спецификации языка Java:

Объект считается полностью инициализируется, когда его конструктор заканчивает Нить, которая может видеть только ссылку на объект после того, как этот объект был полностью инициализирован гарантированно видеть правильно инициализированные значения для этого последние поля объекта.

Модель использования для окончательных полей простой. Установите последние поля для объект в этом объекте конструктор. Не пишите ссылку к объекту, строящемуся в место, где его может увидеть другой поток до того, как конструктор объекта законченный. Если это следует, то когда объект виден другим нить, эта нить всегда будет видеть правильно построенная версия последние поля этого объекта. Будет также увидеть версии любого объекта или массив, на который ссылаются эти последние поля которые, по крайней мере, так же актуальны, как Финальные поля.

Другими словами, ваш слушатель может в конечном итоге увидеть конечные поля со значениями по умолчанию, если он исследует объект в другом потоке. Этого не произойдет, если регистрация слушателя произойдет после того, как конструктор завершит работу.

С точки зрения того, что происходит, я подозреваю, что в самом конце конструктора существует неявный барьер памяти, гарантирующий, что все потоки "видят" новые данные; без этого барьера памяти могут возникнуть проблемы.

6 голосов
/ 25 марта 2010

Другая проблема возникает, когда вы создаете подкласс ThisEscape, и дочерний класс вызывает этот конструктор. Неявная ссылка this в EventListener будет иметь не полностью построенный объект.

2 голосов
/ 25 марта 2010

Между окончанием registerListener и возвратом конструктора есть небольшое, но конечное время. В это время может использовать другой поток и попытаться вызвать doSomething (). Если среда выполнения не вернулась прямо к вашему коду в то время, объект может быть в недопустимом состоянии.

На самом деле я не уверен в java, но один пример, который я могу вспомнить, - это где, возможно, среда выполнения перемещает экземпляр перед возвращением к вам.

Я даю тебе небольшой шанс.

...