Когда именно вы используете ключевое слово volatile в Java?
Я прочитал " Когда использовать" volatile "в Java?", Но я все еще в замешательстве. Как узнать, когда мне следует пометить переменную volatile? Что делать, если я ошибаюсь, либо пропуская volatile на то, что нуждается в этом, либо добавляя volatile на то, что не нужно? Каковы практические правила при определении того, какие переменные должны быть изменчивыми в многопоточном коде?
7 ответов
Вы в основном используете его, когда хотите разрешить доступ к переменной-члену несколькими потоками, но не нуждаетесь в составной атомарности (не уверен, что это правильная терминология).
class BadExample {
private volatile int counter;
public void hit(){
/* This operation is in fact two operations:
* 1) int tmp = this.counter;
* 2) this.counter = tmp + 1;
* and is thus broken (counter becomes fewer
* than the accurate amount).
*/
counter++;
}
}
Выше приведен плохой пример, потому что вам нужна составная атомарность.
class BadExampleFixed {
private int counter;
public synchronized void hit(){
/*
* Only one thread performs action (1), (2) at a time
* "atomically", in the sense that other threads can not
* observe the intermediate state between (1) and (2).
* Therefore, the counter will be accurate.
*/
counter++;
}
}
Теперь верный пример:
class GoodExample {
private static volatile int temperature;
//Called by some other thread than main
public static void todaysTemperature(int temp){
// This operation is a single operation, so you
// do not need compound atomicity
temperature = temp;
}
public static void main(String[] args) throws Exception{
while(true){
Thread.sleep(2000);
System.out.println("Today's temperature is "+temperature);
}
}
}
Теперь, почему вы не можете просто использовать private static int temperature
? На самом деле вы можете (в том смысле, что ваша программа не взорвется или что-то в этом роде), но изменение в temperature
другим потоком может быть или не быть "видимым" для основного потока.
В основном это означает, что это даже возможно, что ваше приложение. продолжает писать Today's temperature is 0
навсегда, если вы не используете volatile
(на практике значение имеет тенденцию становиться в конечном итоге видимым. Однако вы не должны рисковать, если не будете использовать volatile, когда это необходимо, поскольку это может привести к неприятным ошибкам (вызванным незавершенными объектами и т. д.).
Если вы положите volatile
ключевое слово на то, что не нужно volatile
, это не повлияет на правильность вашего кода (то есть поведение не изменится). С точки зрения производительности это будет зависеть от реализации JVM. Теоретически, вы можете получить незначительное снижение производительности, потому что компилятор не может выполнить переупорядочивание оптимизаций, должен лишить законной силы кэш-память процессора и т. Д., Но с другой стороны, компилятор может доказать, что ваше поле никогда не будет доступно нескольким потокам, и устранить эффект volatile
Ключевое слово полностью и скомпилируйте его в идентичные инструкции.
РЕДАКТИРОВАТЬ:
Ответ на этот комментарий:
Хорошо, но почему мы не можем синхронизировать сегодняшнюю температуру и создать синхронизированный геттер для температуры?
Можно и так будет вести себя правильно. Все, что вы можете с volatile
может быть сделано с synchronized
, но не наоборот. Есть две причины, по которым вы можете предпочесть volatile
если ты можешь:
- Менее подвержен ошибкам: это зависит от контекста, но во многих случаях используется
volatile
менее подвержен ошибкам параллелизма, таким как блокировка при удержании блокировки, взаимоблокировки и т. д. - Более производительный: в большинстве реализаций JVM
volatile
может иметь значительно более высокую пропускную способность и лучшую задержку. Однако в большинстве приложений разница слишком мала, чтобы иметь значение.
Volatile наиболее полезно в алгоритмах без блокировок. Вы помечаете переменную, содержащую общие данные, как изменчивую, когда вы не используете блокировку для доступа к этой переменной, и вы хотите, чтобы изменения, сделанные одним потоком, были видны в другом, или вы хотите создать отношение "происходит после", чтобы обеспечить вычисление опять не переупорядочено, чтобы изменения стали видны в соответствующее время.
Поваренная книга JMM описывает, какие операции можно переупорядочить, а какие нет.
volatile
Ключевое слово гарантирует, что значение переменной volatile всегда будет считываться из основной памяти, а не из локального кэша потока.
Из учебника по параллельности Java:
Использование энергозависимых переменных снижает риск ошибок согласованности памяти, потому что любая запись в энергозависимую переменную устанавливает отношение "происходит до" с последующим чтением этой же переменной.
Это означает, что изменения в изменчивой переменной всегда видны другим потокам. Это также означает, что когда поток читает переменную volatile, он видит не только последнее изменение volatile, но также побочные эффекты кода, который привел к изменению.
По вашему запросу:
Как узнать, когда мне следует пометить переменную volatile? Каковы практические правила при определении того, какие переменные должны быть изменчивыми в многопоточном коде?
Если вы чувствуете, что все потоки читателей всегда получают последнее значение переменной, вы должны пометить переменную как volatile
Если у вас есть один поток записи для изменения значения переменной и несколько потоков чтения для чтения значения переменной, модификатор volatile гарантирует согласованность памяти.
Если у вас есть несколько потоков для записи и чтения переменных, volatile
один только модификатор не гарантирует целостность памяти. Ты должен synchronize
код или использовать конструкции параллелизма высокого уровня, такие как Locks
, Concurrent Collections
, Atomic variables
и т.п.
Связанные вопросы SE / статьи:
Объяснение изменчивых переменных в документах Java
Разница между изменчивым и синхронизированным в Java
javarevisited статья
volatile
может также использоваться для безопасной публикации неизменяемых объектов в многопоточной среде.
Объявление поля как public volatile ImmutableObject foo
гарантирует, что все потоки всегда видят текущую доступную ссылку на экземпляр.
См. Java Concurrency на практике для получения дополнительной информации по этой теме.
На самом деле не согласен с примером, приведенным в ответе с наибольшим количеством голосов, насколько мне известно, он НЕ должным образом иллюстрирует изменчивую семантику в соответствии с моделью памяти Java. Volatile имеет более сложную семантику.
В представленном примере главный поток может продолжать печатать "Сегодняшняя температура равна 0" вечно, даже если запущен другой поток, который должен обновлять температуру, если этот другой поток никогда не запланирован.
Лучший способ проиллюстрировать изменчивую семантику - это 2 переменные.
Для простоты мы будем предполагать, что единственный способ обновить две переменные - через метод "setTemperas".
Для простоты предположим, что работают только 2 потока, основной поток и поток 2.
//volatile variable
private static volatile int temperature;
//any other variable, could be volatile or not volatile doesnt matter.
private static int yesterdaysTemperature
//Called by other thread(s)
public static void setTemperatures(int temp, int yestemp){
//thread updates yesterday's temperature
yesterdaysTemperature = yestemp;
//thread updates today's temperature.
//This instruction can NOT be moved above the previous instruction for optimization.
temperature = temp;
}
последние две инструкции присваивания НЕ могут быть переупорядочены в целях оптимизации ни компилятором, ни средой выполнения, ни оборудованием.
public static void main(String[] args) throws Exception{
while(true){
Thread.sleep(2000);
System.out.println("Today's temperature is "+temperature);
System.out.println("Yesterday's temperature was "+yesterdaysTemperature );
}
}
Как только основной поток читает изменчивую переменную температуру (в процессе ее печати),
1) Существует гарантия, что он увидит последнее записанное значение этой изменчивой переменной независимо от того, сколько потоков записывает в нее, независимо от того, в каком методе они обновляются, синхронизированы или нет.
2) Если оператор system.out в основном потоке выполняется, после момента времени, в который поток 2 выполнил оператор температура = температура, как вчерашняя температура, так и сегодняшняя температура гарантированно будут печатать значения, установленные в них потоком 2, когда он запустил оператор температура = темп.
Эта ситуация становится ОЧЕНЬ более сложной, если а) работает несколько потоков и б) существуют и другие методы, кроме только метода setTemperas, которые могут обновлять переменную вчерашнюю температуру и текущую температуру, которые активно вызываются этими другими потоками. Я думаю, что потребовалась бы статья приличного размера, чтобы проанализировать последствия, основанные на том, как Модель памяти Java описывает изменчивую семантику.
Короче говоря, пытаться просто использовать volatile для синхронизации крайне рискованно, и вам лучше придерживаться синхронизации ваших методов.
voltalie означает, что меняйте значение. Значение этой переменной никогда не будет кэшироваться локально: все операции чтения и записи будут идти прямо в "основную память". Другими словами, компилятор Java и Thread, которые не кэшируют значение этой переменной и всегда читают это из основной памяти.
http://mindprod.com/jgloss/volatile.html
"Ключевое слово volatile используется для переменных, которые могут изменяться одновременно другими потоками".
"Так как другие потоки не могут видеть локальные переменные, никогда не требуется помечать локальные переменные как изменчивые. Вам необходимо синхронизировать, чтобы координировать изменения переменных из разных потоков, но часто изменяемые могут просто посмотреть на них".