Как неявные барьеры памяти JVM ведут себя при объединении в цепочку конструкторов?

Что касается моего более раннего вопроса на не полностью созданных объектах, у меня есть второй вопрос. Как Jon Skeet указал, существует неявный барьер памяти в конце конструктора, который удостоверяется это final поля видимы ко всем потокам. Но что, если вызовы конструктора другой конструктор; есть ли такой барьер памяти в конце каждого из них, или только в конце того, который назвали во-первых? Таким образом, когда "неправильное" решение:

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

И корректный был бы версией метода фабрики:

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;
    }
}

Следующее работало бы также, или нет?

public class MyListener {
    private final EventListener listener;

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

    public MyListener(EventSource source) {
        this();
        source.register(listener);
    }
}

Обновление: существенный вопрос, это this() гарантируемый на самом деле назвать частного конструктора выше (в этом случае, где предназначено был бы барьер и все будет безопасно), или действительно ли возможно, что частный конструктор встраивается в общедоступного как оптимизация для сохранения одного барьера памяти (в этом случае не было бы барьера до в конце общедоступного конструктора)?

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

14
задан 14 revs 23 May 2017 в 11:58
поделиться

4 ответа

Экранирование ссылки на объект в c-tor может опубликовать не полностью сконструированный объект. Это верно даже , если публикация является последним оператором в конструкторе .

Ваш SafeListener может работать некорректно в параллельной среде, даже если выполняется встраивание c-tor (что я думаю, что это не так - подумайте о создании объектов с использованием отражения путем доступа к частному c-tor).

1
ответ дан 1 December 2019 в 14:21
поделиться

Ваша вторая версия неверна, потому что она позволяет 'ссылка на выход из строительного процесса. Наличие 'this' escape делает недействительными гарантии безопасности инициализации, которые обеспечивают безопасность конечным полям.

Чтобы ответить на неявный вопрос, барьер в конце построения возникает только в самом конце построения объекта. Интуиция, предложенная одним читателем относительно встраивания, полезна; с точки зрения модели памяти Java, границ методов не существует.

3
ответ дан 1 December 2019 в 14:21
поделиться

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

Это касается также связанных конструкторов.

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

3
ответ дан 1 December 2019 в 14:21
поделиться

РЕДАКТИРОВАТЬ После комментария, который предлагал компилятору встраивать частный конструктор (я не думал об этой оптимизации), есть вероятность, что код будет небезопасным . И худшая часть небезопасного многопоточного кода - это то, что он, кажется, работает, поэтому лучше полностью его избегать. Если вы хотите поиграть в разные трюки (по какой-то причине вы действительно хотите избежать использования фабрики), подумайте о добавлении оболочки, чтобы гарантировать согласованность данных во внутреннем объекте реализации и зарегистрироваться во внешнем объекте.


Думаю, он будет хрупким, но все в порядке. Компилятор не может знать, будет ли внутренний конструктор вызываться только из других конструкторов или нет, поэтому он должен убедиться, что результат будет правильным для кода, вызывающего только внутренний конструктор, поэтому какой бы механизм он ни использовал (барьер памяти?) быть там на месте.

Я предполагаю, что компилятор добавит барьер памяти в конце каждого конструктора. Проблема все еще существует: вы передаете ссылку this на другой код (возможно, другие потоки) до того, как он будет полностью построен - это плохо -, но если единственная «конструкция», которая остается, это зарегистрировав слушателя, то состояние объекта будет максимально стабильным.

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

Кстати: Предполагаемая безопасность может быть неверной. Я не знаю, насколько сложен / умен компилятор, и является ли барьер памяти (или тому подобное) чем-то, что он может попытаться оптимизировать ... поскольку конструктор является частным, компилятор имеет достаточно информации, чтобы знать, что это вызывается только из других конструкторов, и этого достаточно, чтобы определить, что механизм синхронизации не нужен во внутреннем конструкторе ...

1
ответ дан 1 December 2019 в 14:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: