Окончательная и нестабильная гарантия относительно безопасной публикации объектов
Из книги Java параллелизм на практике:
Чтобы безопасно опубликовать объект, и ссылка на объект, и состояние объекта должны быть видны другим потокам одновременно. Правильно сконструированный объект может быть безопасно опубликован:
Инициализация ссылки на объект из статического инициализатора
Хранение ссылки на него в энергозависимом поле или AtomicReference
Сохранение ссылки на него в конечном поле правильно построенного объекта
Сохранение ссылки на него в поле, которое должным образом охраняется
замок.
Мои вопросы:
- Каковы различия между пунктами 2 и 3? Меня интересует разница между
volatile
подход иfinal
подход с точки зрения безопасной публикации объекта. - Что он подразумевает под конечным полем правильно построенного объекта в пункте 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<>());
}
}