Использование ключевого слова volatile с изменяемым объектом
В Java я понимаю, что volatile
Ключевое слово обеспечивает видимость для переменных. Вопрос в том, если переменная является ссылкой на изменяемый объект, volatile
также обеспечить видимость членов внутри этого объекта?
В приведенном ниже примере это работает правильно, если несколько потоков обращаются volatile Mutable m
и изменение value
?
пример
class Mutable {
private int value;
public int get()
{
return a;
}
public int set(int value)
{
this.value = value;
}
}
class Test {
public volatile Mutable m;
}
5 ответов
Это своего рода пояснительная записка о некоторых деталях изменчивости. Написание здесь, потому что это слишком много для комментария. Я хочу привести несколько примеров, которые показывают, как volatile влияет на видимость, и как это изменилось в jdk 1.5.
Учитывая следующий пример кода:
public class MyClass
{
private int _n;
private volatile int _volN;
public void setN(int i) {
_n = i;
}
public void setVolN(int i) {
_volN = i;
}
public int getN() {
return _n;
}
public int getVolN() {
return _volN;
}
public static void main() {
final MyClass mc = new MyClass();
Thread t1 = new Thread() {
public void run() {
mc.setN(5);
mc.setVolN(5);
}
};
Thread t2 = new Thread() {
public void run() {
int volN = mc.getVolN();
int n = mc.getN();
System.out.println("Read: " + volN + ", " + n);
}
};
t1.start();
t2.start();
}
}
Поведение этого тестового кода хорошо определено в jdk1.5+, но не является четко определенным до jdk1.5.
В мире, предшествующем jdk1.5, не было определенной взаимосвязи между изменчивым доступом и энергонезависимым доступом. следовательно, результат этой программы может быть:
- Читать: 0, 0
- Читайте: 0, 5
- Читать: 5, 0
- Читать: 5, 5
В мире jdk1.5 + семантика volatile была изменена, так что volatile-доступ влияет на энергонезависимый доступ точно так же, как и синхронизация. поэтому в мире jdk1.5 + возможны только определенные выходы:
- Читать: 0, 0
- Читайте: 0, 5
- Читать: 5, 0 <- невозможно
- Читать: 5, 5
Вывод 3. невозможен, потому что чтение "5" из энергозависимого _volN устанавливает точку синхронизации между двумя потоками, что означает, что все действия от t1, предпринятые до назначения _volN, должны быть видны для t2.
Дальнейшее чтение:
В вашем примере volatile
Только ключевое слово гарантирует, что последняя ссылка, записанная любым потоком в 'm', будет видна любому потоку, читающему 'm' впоследствии.
Это ничего не гарантирует о вашем get ().
Таким образом, используя следующую последовательность:
Thread-1: get() returns 2
Thread-2: set(3)
Thread-1: get()
для вас вполне законно вернуться 2, а не 3. volatile
ничего не меняет к этому.
Но если вы измените свой Mutable
класс к этому:
class Mutable {
private volatile int value;
public int get()
{
return a;
}
public int set(int value)
{
this.value = value;
}
}
Тогда гарантируется, что второй get()
из темы-1 должен вернуться 3.
Обратите внимание, что volatile
как правило, не лучший метод синхронизации.
В вашем простом примере get / set (я знаю, это всего лишь пример) такой класс AtomicInteger
, используя правильную синхронизацию и фактически предоставляя полезные методы, было бы лучше.
volatile
предоставляет только гарантии относительно ссылки на Объект, который объявлен так. Члены этого экземпляра не синхронизируются.
Согласно Википедии, у вас есть:
- (Во всех версиях Java) Существует глобальный порядок чтения и записи в переменную переменной. Это подразумевает, что каждый поток, обращающийся к изменчивому полю, будет читать свое текущее значение перед продолжением, вместо (потенциально) использования кэшированного значения. (Однако нет никакой гарантии относительно относительного упорядочения изменчивых операций чтения и записи при регулярных операциях чтения и записи, что означает, что это, как правило, бесполезная конструкция потоков.)
- (В Java 5 или более поздней версии) Volatile для чтения и записи устанавливает отношения "до того, как это произошло", очень похоже на получение и освобождение мьютекса.
Итак, в основном то, что у вас есть, что объявив поле volatile
взаимодействие с ним создает "точку синхронизации", после которой любое изменение будет видно в других потоках. Но после этого, используя get()
или же set()
несинхронизирован. Спецификация Java имеет более подробное объяснение.
Использование volatile
а не полностью synchronized
значение по сути является оптимизацией. Оптимизация исходит из более слабых гарантий, предоставленных для volatile
значение по сравнению с synchronized
доступ. Преждевременная оптимизация - корень всего зла; в этом случае зло может быть трудно отследить, потому что оно будет в форме расы и тому подобного. Так что если вам нужно спросить, вы, вероятно, не должны использовать его.
volatile
не "обеспечивает видимость". Единственный эффект заключается в предотвращении кэширования переменной процессором, что обеспечивает отношение "происходит до" при одновременном чтении и записи. Это не влияет на членов объекта и не обеспечивает никакой синхронизации synchronized
замок.
Поскольку вы не сказали нам, каково "правильное" поведение вашего кода, на вопрос нельзя ответить.