Неправильная публикация ссылки на объект Java

Ниже приведен пример книги «Практический курс Java на практике» Брайана Гетца, глава 3, раздел 3.5.1. Это пример ненадлежащей публикации объектов

class someClass { public Holder holder; public void initialize() { holder = new Holder(42); } } public class Holder { private int n; public Holder(int n) { this.n = n; } public void assertSanity() { if (n!=n) throw new AssertionError("This statement is false"); } } 

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

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

Из дальнейшего чтения один из способов решить эту проблему состоит в том, чтобы сделать Холдер неизменным, сделав переменную ‘n’ final. Предположим теперь, что Холдер не является непоколебимым, но эффективно неизменным. Чтобы безопасно публиковать этот объект, нам нужно сделать статическую инициализацию владельца и объявить ее изменчивой (как статической, так и изменчивой или просто изменчивой)? Что-то вроде

 public class someClass { public static volatile Holder holder = new Holder(42); } 

Заранее благодарны за Вашу помощь.

Вы можете себе представить, что создание объекта имеет несколько неатомных функций. Сначала вы хотите инициализировать и опубликовать Holder. Но вам также нужно инициализировать все поля личных членов и публиковать их.

Ну, у JMM нет правил для записи и публикации полей участника- holder – до написания поля holder как происходит в initialie() . Это означает, что даже если holder не является нулевым, законным для полей участника еще не видно других streamов.

Вы можете увидеть что-то вроде

 public class Holder{ String someString = "foo"; int someInt = 10; } 

holder может не иметь значения null, но someString может быть нулевым, а someInt может быть 0.

Под аркой x86 это, из того, что я знаю, невозможно осуществить, но может быть не так в других.

Поэтому может возникнуть следующий вопрос: Why does volatile fix this? JMM говорит, что все записи, которые происходят до энергозависимого хранилища, видны для всех последующих streamов волатильного поля.

Поэтому, если holder неустойчив, и вы видите, что holder не является нулевым, на основе изменчивых правил все поля будут инициализированы.

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

Да, потому что, как я уже говорил, если переменная holder не равна нулю, все записи будут видны.

Как может быть вызвано утверждение?

Если нить отмечает, что holder не имеет значения null и вызывает assertionError при вводе метода и чтении n в первый раз может быть 0 (значение по умолчанию), второе чтение n теперь может видеть запись из первого streamа.

 public class Holder { private int n; public Holder(int n) { this.n = n; } public void assertSanity() { if (n!=n) throw new AssertionError("This statement is false"); } } 

Скажем, что один stream создает экземпляр Holder и передает ссылку на другой stream, который вызывает assertSanity .

Назначение this.n в конструкторе происходит в одном streamе. И два чтения из n встречаются в другом streamе. Единственное, что происходит – до отношения между двумя чтениями. Не происходит – до отношения, связанного с назначением и любым из чтений.

Без каких-либо событий – до отношений операторы могут быть переупорядочены по-разному, поэтому с точки зрения одного streamа this.n = n может произойти после возврата конструктора.

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

Проблема, о которой вы спрашиваете, вызвана оптимизацией JVM и тем фактом, что создание простого объекта:

 MyClass obj = new MyClass() 

не всегда делается по шагам:

  1. Зарезервировать память для нового экземпляра MyClass в куче
  2. Выполнить конструктор для задания внутренних значений свойств
  3. Установите ссылку «obj» на адрес в куче

Для некоторых целей оптимизации JVM может сделать это пошагово:

  1. Зарезервировать память для нового экземпляра MyClass в куче
  2. Установите ссылку «obj» на адрес в куче
  3. Выполнить конструктор для задания внутренних значений свойств

Итак, представьте, хотите ли два streamа получить доступ к объекту MyClass. Сначала создается, но из-за JVM он выполняет «оптимизированный» набор шагов. Если он выполнит только шаги 1 и 2 (но не сделает 3), то у нас может возникнуть серьезная проблема. Если второй stream использует этот объект (он не будет пустым, поскольку он уже указывает на зарезервированную часть памяти в куче), чем свойства будут неправильными, что может привести к неприятным вещам.

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

Класс Holder в порядке, но class someClass может отображаться в несогласованном состоянии – между созданием и вызовом initialize() переменная экземпляра holder имеет значение null .

  • Существует ли ExecutorService, который использует текущий stream?
  • .NET Асинхронный stream чтения / записи
  • Swing - Обновить ярлык
  • Есть ли способ для нескольких процессов совместно использовать прослушивающий сокет?
  • Более эффективный способ для цикла паузы
  • Выбор лучшего списка параллелизма в Java
  • Невозможно создать кэшированный пул streamов с ограничением размера?
  • Когда предпочтительнее использовать volatile boolean в Java, а не AtomicBoolean?
  • Совместные запросы NSURLSession с Alamofire
  • Пункты памяти и стиль кодирования по Java VM
  • Какой алгоритм параллельной сортировки имеет лучшую среднюю производительность?
  • Давайте будем гением компьютера.