Как ведут себя неявные барьеры памяти JVM при построении цепочек конструкторов? - PullRequest
14 голосов
/ 25 марта 2010

Обращаясь к моему более раннему вопросу о не полностью построенных объектах , у меня есть второй вопрос. Как отметил Джон Скит, в конце конструктора есть неявный барьер памяти, который гарантирует, что поля 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() определены где-то точно? Если нет, то я думаю, что мы должны предположить, что встроенные цепные конструкторы разрешены, и, возможно, некоторые JVM или, возможно, даже javac делают это.

Ответы [ 5 ]

6 голосов
/ 24 апреля 2012

Я думаю, что это безопасно, поскольку модель памяти Java утверждает, что:

Пусть o будет объектом, а c будет конструктором для o , в котором финал поле f записано. Действие по замораживанию конечного поля f из o когда c выходит нормально или внезапно. Обратите внимание, что если один конструктор вызывает другой конструктор, а вызываемый конструктор устанавливает последнее поле, замораживание последнего поля происходит в конец вызванного конструктора.

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

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

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

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

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

Это относится и к цепочечным конструкторам.

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

1 голос
/ 25 марта 2010

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

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

1 голос
/ 25 марта 2010

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


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

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

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

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

...