Окончательная и нестабильная гарантия относительно безопасной публикации объектов

Из книги Java параллелизм на практике:

Чтобы безопасно опубликовать объект, и ссылка на объект, и состояние объекта должны быть видны другим потокам одновременно. Правильно сконструированный объект может быть безопасно опубликован:

  • Инициализация ссылки на объект из статического инициализатора

  • Хранение ссылки на него в энергозависимом поле или AtomicReference

  • Сохранение ссылки на него в конечном поле правильно построенного объекта

  • Сохранение ссылки на него в поле, которое должным образом охраняется
    замок.

Мои вопросы:

  1. Каковы различия между пунктами 2 и 3? Меня интересует разница между volatile подход и final подход с точки зрения безопасной публикации объекта.
  2. Что он подразумевает под конечным полем правильно построенного объекта в пункте 3? До начала маркированных пунктов авторы уже упоминали, что речь идет о правильно сконструированном объекте (который, как я полагаю, не позволяет this ссылка на побег). Но еще раз, почему они упомянули о правильно построенных объектах?

3 ответа

Решение

Каковы различия между пунктами 2 и 3?

  • volatile в основном означает, что любые записи в это поле будут видны из других потоков. Поэтому, когда вы объявляете поле как volatile: private volatile SomeType field;, вам гарантировано, что если конструктор пишет в это поле: field = new SomeType();это назначение будет видно другим потокам, которые впоследствии пытаются прочитать field,
  • final имеет очень похожую семантику: у вас есть гарантия, что если у вас есть последнее поле: private final SomeType field; запись в это поле (либо в объявлении, либо в конструкторе): field = new SomeType(); не будет перезаписан и будет виден другим потокам, если объект правильно опубликован (т.е. this например).

Очевидно, что главное отличие состоит в том, что если поле является окончательным, его можно назначить только один раз.

Что он подразумевает под конечным полем правильно построенного объекта в пункте 3?

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


Придуманный пример:

class SomeClass{
    private final SomeType field;

    SomeClass() {
        new Thread(new Runnable() {
            public void run() {
                SomeType copy = field; //copy could be null
                copy.doSomething(); //could throw NullPointerException
            }
        }).start();
        field = new SomeType();
    }
}

Там нет разницы в том, что последствия публикации volatile против final, Кроме этого final может быть установлен только один раз в конструкторе, и поэтому то, что вы читаете, никогда не должно меняться.

Я считаю, что правильно построенный объект - это то, на что вы ссылаетесь, объект, чей this ссылка не избежала своего конструктора и была безопасно опубликована в потоке, в котором она используется.

Я думаю, что в маркер 2 следует добавить ограничение: « правильно построенного объекта »:

Сохранение ссылки на него в изменчивом поле или AtomicReference правильно сконструированного объекта .

Если использовать только изменчивое поле без правильно сконструированного объекта, объект 'map' не будет безопасно опубликован, как показано в следующем коде:

      import java.util.HashMap;

public class SafePublish {

volatile HashMap map;

SafePublish(HashMap map) throws InterruptedException {
    new Thread(new Runnable() {
        @Override
        public void run() {
            // here prints null, which means 'map' is not safely published
            System.out.println(SafePublish.this.map);
        }
    }).start();
    Thread.sleep(5000);
    this.map = map;
}

public static void main(String[] args) throws InterruptedException {
    SafePublish safePublishInstance = new SafePublish(new HashMap<>());
  }
}
Другие вопросы по тегам