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

Я прочитал " Когда использовать" volatile "в Java?" но я все еще смущен. Как узнать, когда я должен отметить переменную изменчивость? Что делать, если я ошибаюсь, либо опуская волатильность на что-то, что ему нужно, или накладывает изменчивость на то, что нет? Каковы правила большого пальца при определении того, какие переменные должны быть 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 temperature is "+temperature);
        }
    }
}

Теперь, почему вы не можете использовать private static int temperature? На самом деле вы можете (в том смысле, что ваша программа не взорвется или что-то еще), но изменение в temperature на другой поток может быть или не быть "видимым" для основного потока.

В основном это означает, что это даже возможно, что ваше приложение. продолжает писать Today temperature is 0 навсегда, если вы не используете volatile (на практике это значение, как правило, становится видимым в конечном итоге. Однако при необходимости вы не должны рисковать не использовать летучие, поскольку это может привести к неприятным ошибкам (вызванным - полностью сконструированные объекты и т.д.).

Если вы поместите ключевое слово volatile на то, что не нуждается в volatile, это не повлияет на правильность кода (т.е. поведение не изменится). Что касается производительности, это будет зависеть от реализации JVM. Теоретически вы можете получить крошечную деградацию производительности, потому что компилятор не может выполнять переупорядочивание оптимизаций, должен аннулировать кеш процессора и т.д., Но опять же компилятор может доказать, что ваше поле никогда не может быть доступно несколькими потоками и удалить эффект volatile полностью и скомпилировать его с идентичными инструкциями.

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Подробнее см. Java Concurrency на практике.

Ответ 4

Ключевое слово

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

Из java concurrency учебник:

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

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

По вашему запросу:

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

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

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

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

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

Объяснение переменных volatile в документах Java

Разница между изменчивой и синхронизированной в Java

javarevisited статья

Ответ 5

Собственно не согласен с примером, приведенным в верхнем голосовавшем ответе, насколько мне известно, он НЕ правильно иллюстрирует изменчивую семантику в соответствии с моделью памяти Java. У Летучих есть более сложная семантика.

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

Лучший способ проиллюстрировать изменчивую семантику - это 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 temperature
    yesterdaysTemperature = yestemp;
    //thread updates today temperature. 
    //This instruction can NOT be moved above the previous instruction for optimization.
    temperature = temp;
   }

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

public static void main(String[] args) throws Exception{
    while(true){
       Thread.sleep(2000);
       System.out.println("Today temperature is "+temperature); 
       System.out.println("Yesterday temperature was "+yesterdaysTemperature );
 }
}

Как только основной поток прочитает изменчивую переменную температуру (в процессе ее печати),

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

2) Если оператор system.out в основном потоке запущен, то после момента, когда поток 2 выполнил инструкцию temperature = temp, обе вчерашняя температура и сегодняшняя температура будут гарантированы для печати значений, установленных в них, нить 2, когда он выполнил инструкцию temperature = temp.

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

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

Ответ 6

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

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

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

Ответ 7

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