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

StackOverflow https://stackoverflow.com/questions/2513841

Вопрос

Ссылаясь на мой Предыдущий вопрос о не полностью сконструированных объектах, У меня есть второй вопрос. Как отметил Джон Скит, в конце конструктора есть неявный барьер памяти, который гарантирует, что 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 или, может быть, даже даже javacS делают это.

Это было полезно?

Решение

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

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

Другие советы

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

Это применимо также для цепных конструкторов.

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

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

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

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


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

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

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

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

Выход из ссылки на объект в C-TOR может опубликовать не полностью сконструированный объект. Это правда даже Если публикация является последним утверждением в конструкторе.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top