Когда именно вы используете ключевое слово volatile в Java?


7 79

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, это не повлияет на правильность вашего кода (т. е. поведение не изменится). С точки зрения производительности это будет зависеть от реализации СПМ. Теоретически вы можете получить небольшое снижение производительности, потому что компилятор не может выполнять оптимизацию переупорядочения, должен аннулировать кэш процессора и т. д., но опять же компилятор может доказать, что ваше поле никогда не может быть доступно несколькими потоками и удалить эффект volatile ключевое слово полностью и скомпилируйте его в идентичные инструкции.

EDIT:
Ответ на этот комментарий:

хорошо, но почему мы не можем сделать todaystemperature синхронизированным и создать синхронизированный геттер для температуры?

вы можете, и он будет вести себя правильно. Все, что вы можете с volatile можно сделать synchronized, но не наоборот. Есть две причины, по которым вы можете предпочесть volatile если вы можете:

  1. меньше ошибок: это зависит от контекста, но во многих случаях с помощью volatile менее склонен к ошибкам параллелизма, таким как блокировка при удержании блокировки, взаимоблокировки и т. д.
  2. более производительный: в большинстве реализаций JVM,volatile может иметь значительно более высокую пропускную способность и лучшую задержку. Однако в большинстве приложений разница слишком мала, чтобы иметь значение.

Volatile наиболее полезен в алгоритмах без блокировки. Вы отмечаете переменную, содержащую общие данные, как изменчивую, когда вы не используете блокировку для доступа к этой переменной, и вы хотите, чтобы изменения, сделанные одним потоком, были видны в другом, или вы хотите создать отношение "происходит-после", чтобы гарантировать, что вычисление не переупорядочено, снова, чтобы гарантировать, что изменения станут видимыми в соответствующее время.

The JMM Cookbook описывает, какие операции могут быть переупорядочены и какие не может.

The volatile также может использоваться для безопасной публикации неизменяемых объектов в многопоточной среде.

объявление поля типа public volatile ImmutableObject foo гарантирует, что все потоки всегда видят доступную в данный момент ссылку на экземпляр.

посмотреть параллелизм Java на практике подробнее на эту тему.

volatileключевое слово гарантирует, что значение переменной volatile всегда будет считываться из основной памяти, а не из локального кэша потока.

от параллелизма java учебник :

использование изменчивых переменных снижает риск ошибок согласованности памяти, поскольку любая запись в изменчивую переменную устанавливает связь "происходит до" с последующим чтением этой же переменной

этот означает, что изменения изменчивой переменной всегда видны другим потокам. Это также означает, что когда поток читает переменную volatile, он видит не только последнее изменение volatile, но и побочные эффекты кода, которые привели к изменению.

касательно вашего запроса:

как узнать, когда я должен отметить переменную volatile? Каковы эмпирические правила при выяснении того, какие переменные должны быть изменчивыми в многопоточном режиме код?

если вы чувствуете, что все потоки чтения всегда получают последнее значение переменной, вы должны пометить переменную как volatile

если у вас есть один поток записи для изменения значения переменной и несколько потоков чтения для чтения значения переменной, Летучий модификатор гарантирует согласованность памяти.

если у вас есть несколько потоков для записи и чтения переменных,volatile модификатор сам по себе не гарантирует согласованность памяти. Вы придется synchronize код или использовать высокий уровень параллелизм конструкции типа Locks,Concurrent Collections,Atomic variables etc.

связанные с SE вопросы / статьи:

объяснение изменчивой переменной в Java docs

разница между volatile и synchronized в Java

javarevisited статьи

на самом деле не согласен с примером, приведенным в топ-проголосовали ответ, на сколько мне известно это не правильно проиллюстрировать изменчивую семантику в соответствии с моделью памяти Java. Volatile имеет более сложную семантику.

в приведенном примере основной поток может продолжать печатать" сегодняшняя температура равна 0 " навсегда, даже если есть другой поток, который должен обновить температуру, если этот другой поток никогда не будет запланирован.

A лучший способ проиллюстрировать изменчивую семантику - это использовать 2 переменные.

для простоты предположим, что единственный способ обновить две переменные - это метод "setTemperatures".

для простоты будем считать, что работает только 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) Если система.оператор out в главном потоке выполняется,после время в тот момент, когда поток 2 запустил оператор temperature = temp, как вчерашняя температура, так и сегодняшняя температура будут гарантированно печатать значения, установленные в них потоком 2, когда он запустил оператор temperature=temp.

эта ситуация получает много сложнее, если a) запущено несколько потоков и b) существуют другие методы, кроме метода setTemperatures, которые могут обновлять переменную вчерашней температуры и сегодняшней температуры, которые активно вызывается этими другими потоками. Я думаю, что потребуется статья приличного размера для анализа последствий, основанных на том, как модель памяти Java описывает изменчивую семантику.

http://mindprod.com/jgloss/volatile.html

"ключевое слово volatile используется для переменных, которые могут быть изменены одновременно с другими потоками."

" поскольку другие потоки не могут видеть локальные переменные, никогда нет необходимости отмечать локальные переменные volatile. Вам нужно синхронизировать, чтобы координировать изменения переменных из разных потоков, но часто volatile будет просто смотреть на них."

voltalie означает постоянно изменять значение.Значение этой переменной никогда не будет кэшироваться потоком-локально: все чтения и записи будут идти прямо в "основную память".другими словами, компилятор Java и поток, которые не кэшируют значение этой переменной и всегда считывают ее из основной памяти.