Когда именно вы используете ключевое слово volatile в Java?
Я прочитал "Когда использовать 'volatile' в Java?" но я все еще в замешательстве. Как я узнаю, когда мне следует пометить переменную volatile? Что, если я пойму неправильно, либо опущу volatile для чего-то, что в нем нуждается, либо добавлю volatile для чего-то, что этого не делает? Каковы эмпирические правила при определении того, какие переменные должны быть volatile в многопоточном коде?
Переведено автоматически
Ответ 1
В основном вы используете его, когда хотите разрешить доступ к переменной-члену нескольким потокам, но не нуждаетесь в составной атомарности (не уверен, что это правильная терминология).
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
другим потоком может быть, а может и не быть "видимым" для основного потока.
По сути, это означает, что возможно даже, что ваше app. продолжает писать Today's temperature is 0
вечно, если вы не используете volatile
(на практике значение имеет тенденцию со временем становиться видимым. Однако вам не следует рисковать, не используя volatile при необходимости, поскольку это может привести к неприятным ошибкам (вызванным неполностью сконструированными объектами и т.д.).
Если вы добавите volatile
ключевое слово к чему-то, что не требуется volatile
, это не повлияет на корректность вашего кода (т. Е. Поведение не изменится). С точки зрения производительности, это будет зависеть от реализации JVM. Теоретически вы можете получить небольшое снижение производительности, потому что компилятор не может выполнить оптимизацию переупорядочения, придется аннулировать кэш процессора и т.д., Но опять же, компилятор может доказать, что к вашему полю никогда не смогут получить доступ несколько потоков, и полностью удалить эффект volatile
ключевое слово и скомпилировать его по идентичным инструкциям.
Редактировать:
Ответ на этот комментарий:
Хорошо, но почему мы не можем синхронизировать todaysTemperature и создать синхронизированный геттер для определения температуры?
Вы можете, и оно будет вести себя корректно. Все, что вы можете сделать с помощью volatile
, можно сделать с помощью synchronized
, но не наоборот. Есть две причины, по которым вы могли бы предпочесть, volatile
если вы можете:
- Меньше подвержен ошибкам: это зависит от контекста, но во многих случаях использование
volatile
менее подвержено ошибкам параллелизма, таким как блокировка при удержании блокировки, взаимоблокировки и т.д. - Более производительный: в большинстве реализаций JVM,
volatile
может иметь значительно более высокую пропускную способность и лучшую задержку. Однако в большинстве приложений разница слишком мала, чтобы иметь значение.
Ответ 2
Volatile наиболее полезен в алгоритмах без блокировок. Вы помечаете переменную, содержащую общие данные, как volatile, когда вы не используете блокировку для доступа к этой переменной и хотите, чтобы изменения, внесенные одним потоком, были видны в другом потоке, или вы хотите создать отношение "происходит после", чтобы гарантировать, что вычисления не будут переупорядочены, опять же, чтобы гарантировать, что изменения станут видны в соответствующее время.
В JMM Cookbook описывается, какие операции можно упорядочивать, а какие нельзя.
Ответ 3
volatile
ключевое слово гарантирует, что значение переменной volatile всегда будет считываться из основной памяти, а не из локального кэша потока.
Из учебника по параллелизму Java :
Использование volatile переменных снижает риск ошибок согласованности памяти, поскольку любая запись в volatile переменную устанавливает связь "происходит до" с последующими чтениями этой же переменной
Это означает, что изменения переменной volatile всегда видны другим потокам. Это также означает, что когда поток считывает переменную volatile , он видит не только последнее изменение переменной volatile, но и побочные эффекты кода, который привел к изменению.
Что касается вашего запроса:
Как я узнаю, когда мне следует пометить переменную volatile? Каковы практические правила при определении того, какие переменные должны быть volatile в многопоточном коде?
Если вы чувствуете, что все потоки чтения всегда получают последнее значение переменной, вы должны пометить переменную как volatile
Если у вас есть один поток записи для изменения значения переменной и несколько потоков чтения для чтения значения переменной, модификатор volatile гарантирует согласованность памяти.
Если у вас есть несколько потоков для записи и чтения переменных, volatile
один только модификатор не гарантирует согласованности памяти. Вы должны synchronize
доработать код или использовать высокоуровневые конструкции с параллелизмом, такие как Locks
, Concurrent Collections
, Atomic variables
и т.д.
Вопросы по SE по теме / статьи:
Объяснение переменной Volatile в Java docs
Разница между volatile и synchronized в Java
javarevisited статья
Ответ 4
volatile
Также может использоваться для безопасной публикации неизменяемых объектов в многопоточной среде.
Объявление поля типа public volatile ImmutableObject foo
гарантирует, что все потоки всегда будут видеть ссылку на доступный в данный момент экземпляр.
Подробнее об этом читайте в разделе Параллелизм Java на практике.