Как частные конструкторы обеспечивают безопасность публикации в Java

В классической книге «Параллелизм в Java на практике» Брайан Гетц использует следующий фрагмент кода, чтобы продемонстрировать, как безопасно опубликовать объект с помощью закрытого конструктора и фабричного метода:

public class SafeListener {
    private final EventListener listener;

    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
    }

    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}

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

Мне известно, что закрытый конструктор используется для предотвращения создания экземпляров вне объекта, но как это относится к потоку, а не к объекту? Поток не обязательно является объектом, и я не понимаю, что не позволяет другому потоку получить ссылку на safe до того, как конструктор завершит выполнение.


person kstratis    schedule 04.02.2014    source источник
comment
stackoverflow.com/questions/12611366/ Ответы, приведенные здесь обсудить части вашего вопроса. Конструкторы действительно не синхронизированы и могут выполнять описанное вами поведение. Решение может заключаться в том, чтобы вместо этого инициализировать экземпляр в синхронизированном методе, но я могу ошибаться.   -  person Joetjah    schedule 04.02.2014


Ответы (3)


Свойство конструктора быть private не имеет ничего общего с потокобезопасностью. Это пример гарантии публикации поля final. Чтобы он работал, экземпляр this поля final не должен исчезать во время конструктора, поэтому фабричный метод сначала заботится о создании экземпляра держателя и регистрации прослушивателя затем. эм>. Для приложений фабричного шаблона естественно иметь private конструкторов и public фабричных методов, но здесь это не важно для обеспечения потокобезопасности.

Все дело в том, как JIT и оптимизатор HotSpot обрабатывают код при выполнении оптимизации. Они знают, что такое поля final или volatile и что такое конструктор. Они будут подчиняться ограничениям на степень оптимизации таких конструкций. Самое главное, они гарантируют, что все записи в объект, который вы храните в поле final, и запись в само поле final происходит до завершения конструктора, так что поле final сохраняет происходит до любой эффект вызова registerListener в вашем примере. Поэтому другие потоки не могут видеть ссылку на прослушиватель до ее корректной инициализации.

Обратите внимание, что это то, на что вы редко должны полагаться. В вашем примере, если метод registerListener из EventSource не является потокобезопасным, все равно могут произойти очень плохие вещи. С другой стороны, если он потокобезопасен, его потокобезопасность будет применяться и к прослушивателю, созданному до регистрации, поэтому гарантия поля final не потребуется.

person Holger    schedule 04.02.2014

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

Пример потокобезопасного API.

person Victor Sorokin    schedule 04.02.2014

как это относится к потоку, а не к объекту? Поток не обязательно является объектом, и я не вижу, что мешает другому потоку получить ссылку на сейф до того, как конструктор завершит выполнение.

Разумеется, Thread всегда является объектом в java.lang.Thread смысле. Узор не следует наносить на саму нить. Вместо этого его можно было бы применять для случаев, когда новый поток должен быть запущен «вместе» с построением объекта. Запуск потока В конструкторе может привести к выходу ссылки на незавершенно построенный объект. Однако с этим шаблоном вновь созданный экземпляр попадает в ловушку метода newInstance до тех пор, пока его построение не будет полностью завершено.

(Или, скажем так: я не могу представить, как другой поток должен получить ссылку на экземпляр safe до того, как его построение будет завершено. Может быть, вы можете привести пример, как вы думаете, что это может произойти .)

person Marco13    schedule 04.02.2014
comment
Итак, вы говорите, что если я делаю myObject = newInstance(event), объект назначается только после того, как он полностью построен. Однако, пока он создается, может ли другой поток каким-то образом вмешаться и взять под контроль safe? Я считаю, что это возможно, но поскольку SafeListener safe выполняется в стеке вызывающего потока (потому что он является локальным, пока этот поток не вернет его), он в конечном итоге не может. Во всяком случае, я немного запутался... - person kstratis; 04.02.2014
comment
@Konos5: с точки зрения создающего потока все всегда происходит в правильном порядке, поэтому локально созданный объект не может быть виден другим до регистрации слушателя. Однако из-за некоторых оптимизаций другие потоки могут воспринимать эффекты в другом порядке. Безопасная публикация поля final предотвращает некоторые оптимизации. - person Holger; 04.02.2014
comment
Что касается пока, может ли другой поток каким-то образом вмешаться и взять под контроль безопасный, ответ таков: Нет. Когда другой поток вводит этот метод, он не сможет получить доступ к тому же safe объекту. В методе объект safe существует только в стеке. И каждый поток имеет свой собственный стек. Поэтому, когда другой поток входит в этот метод, он будет иметь доступ только к своему СОБСТВЕННОМУ, новому экземпляру safe, но не к тому, который был создан первым потоком. - person Marco13; 04.02.2014
comment
Верно, нет необходимости в конструкциях потокобезопасности для локальных объектов. Важным моментом является то, что потоки, проходящие через список слушателей, зарегистрированных в EventSource, могут видеть этот экземпляр. И без потокобезопасных конструкций они могут видеть его, но не правильные значения его полей экземпляра. Как сказано в моем ответе, если регистрация источника/слушателя событий является потокобезопасной, нет необходимости в специальной фабрике прослушивателей. - person Holger; 04.02.2014
comment
Итак, насколько я понимаю, все сводится к 2 основным угрозам: 1) потоки 'Rampage', которые хотят взять под контроль 'safe', но не могут вызвать безопасный создается внутри метода, что означает, что он ограничен потоком, и поэтому каждый поток будет иметь свою собственную копию. 2) Потоки «противного итератора» просматривают прослушиватели, заявляющие об этом экземпляре конечного поля, но снова терпят неудачу, потому что сейф не будет зарегистрирован против прослушивателей до завершения его конструктора, и, таким образом, они выиграли не быть в состоянии видеть это. - person kstratis; 04.02.2014