Volatile для ссылочного типа-всегда ли он избегает публикации ссылок из-за JMM?
предполагаю, что это класс:
public class AmIThreadSafe {
private int a;
private int b;
AmIThreadSafe(int a, int b) {
this.a = a;
this.b = b;
}
}
предполагая, что ссылка этого экземпляра на этот класс (объявлена как volatile
) доступен некоторыми потоками (ведущими к состоянию гонки), как только this
(ссылка) escapes:
volatile AmIThreadSafe instance = new AmIThreadSafe(1,2);
вот, я уверен, что факт присвоения instance
ссылка происходит-перед читать нитями.
а как же AmIThreadSafe's
полей?
делает внешний volatile
ключевое слово также подразумевается happens-before
отношении в отношении a
и b
полей?
Или, скорее, можно закончить с любым потоком, видящим устаревшие значения (значения по умолчанию 0
в этом случае с int
) из-за переупорядочивания потенциальных операторов во время конструктора?
другими словами, Я должен объявить a
и b
final
или volatile
чтобы предотвратить любые сюрпризы с JMM или просто указывает volatile
по ссылке экземпляра достаточно?
----------------ОБНОВЛЕННЫЙ ПОСТ-ХОРОШИЙ ОТВЕТ:----------------------------
следующая статья подтверждает своим образцом, что в моем случае,a
и b
защищены от оптимизации JMM, которые предотвращают постоянное происходит-перед отношения.
http://jeremymanson.blogspot.fr/2008/11/what-volatile-means-in-java.html
6 ответов
объявления instance
as volatile
не делает свои поля volatile
, но если я правильно понял ваш вопрос, то - да, этого достаточно в вашем случае.
Per §17.4.5 спецификации:
- a
volatile
запись в одном потоке происходит-перед все последующиеvolatile
читать в другом потоке. - операторы в том же потоке имеют происходит-перед отношения, которые вы бы ожидать.
- происходит-перед отношения являются транзитивными.
Итак, если нить воспринимает instance
Как инициализированный, то инициализация instance
случилось-перед это и инициализации instance
'поля S случилось-перед что, так нить будет воспринимать instance
поля как инициализированные.
нет этого недостаточно, чтобы сделать его изменчивым. Однако безопасность резьбы зависит от использования. Например, это все равно может привести к неожиданным результатам, если другой поток изменяет значения.
предполагая, что public
переменные для простоты
volatile AmIThreadSafe instance = new AmIThreadSafe(1,2);
if (instance.x == 0) {
// instance.x might have changed between checking and assigning
instance.x = instance.x + 1;
}
volatile
применим только к переменной (например, x
и y
автоматически volatile
просто так instance
is). Это должно быть ясно из JLS 8.3.1.4
на volatile
в вашем случае применяется только к ссылке AmlThreadSafe
. Вам все равно нужно сделать переменные экземпляра (a
и b
) volatile
или получить к ним доступ в synchronized
блок. В противном случае вы можете получить устаревшие данные.
да.
thread 1 thread 2
1 write(a)
2 write(instance)
3 read(instance)
4 read(a)
поскольку экземпляр является изменчивым, [2] происходит-до [3].
Так как происходит-до является транзитивным, мы имеем hb(1,2), hb(2,3), hb(3,4), поэтому hb (1,4)
если a
и b
изменяются только в конструкторе, то в этом случае вы должны быть в порядке, потому что объект создается (и a
и b
set) перед назначением ссылки на instance
, и любые другие потоки не будут иметь локально кэшированных копий памяти в этих местах, потому что это новый объект, который поток не мог видеть раньше. Другими словами, Я не верю, что возможно, что другой поток когда-либо сможет увидеть значение "по умолчанию" 0 для a
и b
потому что конструктор будет работать полностью до того, как ссылка на объект будет назначена instance
.
a
и b
можно изменить после конструктора, тогда другие ответы здесь верны - вам нужна синхронизация вокруг них.
если вы собираетесь предположить, что a
и b
не будет изменен вне конструктора, тогда нет причин не делать их окончательными в любом случае, просто для безопасности.
пример :
class Something{
private volatile static Something instance = null;
private int x;
private int y;
private Something(){
this.x = 1;
this.y = 2;
}
public static Something getInstance() {
if (instance == null) {
synchronized (Something.class) {
if (instance == null)
instance = new Something();
}
}
}
return instance;
}
}
объяснение :
допустим, у нас есть приведенный выше код:
теперь предположим, что экземпляр не является изменчивым в течение некоторого времени:
нить#1 :
Входит в метод invoke getInstance, проверяет, например, значение{since null}, будет входить в условие IF, доступ к блокировке теперь снова находит этот экземпляр == null, вызывает что-то конструктор. Теперь идет внутри тела конструктора.
Как только поток#1 входит в тело конструктора, происходит переключение контекста, и теперь поток #2 получает поворот для выполнения.
нить#2 :
Вызывает экземпляр get, но внезапно обнаруживает, что экземпляр не равен null?Почему{разум обсудит сразу после этого}и, следовательно, назначит частично построенный объект ссылке и вернет его.
теперь ситуация такова:нить#1 еще нужно построить объект Полностью{необходимо полностью построить его} и нить#2 получил ссылку на частично построенный объект, и если он использует его, как, скажем, ссылку.x / / будет печатать значение по умолчанию "x", а не"1"
почему частично построенная ссылка на объект возвращается в случае нить#2 ? Причина проста: Заявления Перенумерации. Шаги просты для создания объекта и ссылки ассоциации:
- Выделить Память в кучи.
- выполнить тело конструктора, который будет инициализировать члены класса.
- после того, как выше шаг завершен, ссылка на вновь созданный объект.
но иногда компилятор может выполнить эти инструкции не работает, что означает :
Это может произойти примерно так:
- выделить память в куче.
- ссылка на вновь созданный объект.
- выполнить тело Конструктор, который инициализирует члены класса.
Как только эти два шага происходят, и если происходит переключение контекста, то ссылка будет указывать на объект, который не инициализирован, или может быть так, что внутри тела конструктора происходит переключение контекста, тогда в этом случае ссылка будет ссылаться на частично инициализированный объект.
если такой сценарий происходит, то ссылка не будет ни нулевой, ни полной, и, следовательно, это нарушит нашу Одноэлементную мотивацию.
А Как летучие спасет нашу жизнь от таких конфузов :
Как известно Летучая работа с двумя принципами: 1)видимость 2) Происходит Перед Отношениями.
Теперь Происходит Перед Отношениями входит в картину здесь.
поэтому ссылка является изменчивой записью, поэтому все операторы должны произойти до любой изменчивой записи.Опять же, если мы посмотрим на наши этапы строительства объекта:
- выделить память для объекта
- инициализировать переменные-члены{тело конструктора}
- назначьте ссылку на объект экземпляру переменной volatile.
Шаг 3 и переменная Volatile write и, как происходит раньше .. Все заявления писать гарантированно будут доступны для шага 3. И поскольку он является изменчивым, поэтому никакого переупорядочения не произойдет между изменчивыми и энергонезависимыми заявлениями, чего не было в Старая Модель Памяти Java.
Таким образом, перед выполнением шага 3, Шаг 1 и Шаг 2 гарантированно будут выполнены и доступны для шага 3. {В каком порядке происходит Шаг 1 и Шаг 2, мы не беспокоимся об этом.}
Так что из этой темы будет полностью создан объект или null