Вы когда-нибудь использовали ключевое слово volatile в Java?

на работе сегодня я наткнулся на volatile ключевое слово в Java. Не будучи хорошо знаком с ним, я нашел такое объяснение:--2-->

теория и практика Java: управление волатильности

учитывая детали, в которых эта статья объясняет ключевое слово в вопросе, вы когда-нибудь использовать его или вы когда-нибудь видели случай, в котором вы могли бы использовать это ключевое слово в правильном порядке?

21 ответов


volatile имеет семантику для видимости памяти. В принципе, значение a volatile поле становится видимым для всех читателей (в частности, для других потоков) после завершения операции записи. Без volatile читатели могли видеть некоторые не обновленное значение.

отвечая на ваш вопрос: Да, я использую volatile переменная для управления тем, продолжает ли некоторый код цикл. Цикл проверяет volatile значение и продолжается, если оно true. Условие может быть установлено в false вызывая a метод "стоп". Петля видит false и завершается, когда он проверяет значение после завершения выполнения метода stop.

книги "параллелизм Java на практике, " который я настоятельно рекомендую, дает хорошее объяснение volatile. Эта книга написана тем же человеком, который написал статью IBM, на которую ссылаются в вопросе (фактически, он цитирует свою книгу в нижней части этой статьи). Мое использование volatile - Это то, что его статья называет " статусом шаблона 1 флаг."

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


"... модификатор volatile гарантирует, что любой поток, считывающий поле, увидит последнее записанное значение." - Джош Блох

Если вы думаете об использовании volatile, читайте на упаковке java.util.concurrent что касается атомных поведения.

В Википедии на Синглтон Шаблон показывает volatile в использовании.


важный момент, о volatile:

  1. Синхронизация в Java возможна с помощью ключевых слов Java synchronized и volatile и замки.
  2. в Java мы не можем иметь synchronized переменной. Используя synchronized сайта с переменной является незаконным и приведет к ошибке компиляции. Вместо использования synchronized переменная в Java, вы можете использовать Java volatile переменная, которая будет инструктировать потоки JVM читать значение volatile переменной от основная память и не кэшируйте ее локально.
  3. если переменная не разделяется между несколькими потоками, то нет необходимости использовать volatile ключевое слово.

источник

пример использования volatile:

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

мы создаем экземпляр лениво во время первого запроса.

если мы не сделать _instance переменная volatile затем поток, который создает экземпляр Singleton не может связаться с другим потоком. Поэтому, если поток A создает одноэлементный экземпляр и сразу после создания процессор повреждает и т. д., Все остальные потоки не смогут увидеть значение _instance как не null, и они будут считать, что он по-прежнему назначен null.

почему это происходит? Поскольку потоки чтения не выполняют блокировку и пока поток записи не выйдет из синхронизированного блока, память не будет синхронизирована и значение _instance будет не обновляться в основной памяти. С ключевым словом Volatile в Java это обрабатывается самой Java, и такие обновления будут видны всем потокам чтения.

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

пример использования без volatile:

public class Singleton{    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance(){   
          if(_instance == null){  
              synchronized(Singleton.class){  
               if(_instance == null) _instance = new Singleton(); 
      } 
     }   
    return _instance;  
    }

приведенный выше код не является потокобезопасным. Хотя он проверяет значение раз опять же в синхронизированном блоке (по соображениям производительности) компилятор JIT может переставить байт-код таким образом, чтобы ссылка на экземпляр была установлена до завершения конструктором своего выполнения. Это означает, что метод getInstance() возвращает объект, который не может быть полностью инициализирован. Чтобы сделать код потокобезопасным, ключевое слово volatile можно использовать начиная с Java 5 для переменной экземпляра. Переменные, помеченные как volatile, становятся видимыми только для других потоков после конструктор объекта завершил его выполнение полностью.
источник

enter image description here

volatile использование в Java:

итераторы fail-fast-это обычно реализовано с помощью volatile счетчик в объекте списка.

  • при обновлении списка счетчик увеличивается.
  • когда Iterator создается, текущее значение счетчик встроен в

volatile очень полезно для остановки потоков.

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

шаблон, который я использую для потоков:

public class Foo extends Thread {
  private volatile boolean close = false;
  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

обратите внимание, что нет необходимости в синхронизации


один общий пример использования volatile использовать volatile boolean переменная как флаг для завершения потока. Если вы запустили поток и хотите иметь возможность безопасно прерывать его из другого потока, вы можете периодически проверять флаг потока. Чтобы остановить это, установите флаг в true. Сделав флаг volatile, вы можете убедиться, что поток, который проверяет его, увидит, что он был установлен в следующий раз, когда он проверяет его, даже не используя synchronized заблокировать.


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


никто не упомянул обработку операции чтения и записи для длинного и двойного переменного типа. Чтение и запись являются атомарными операциями для ссылочных переменных и для большинства примитивных переменных, за исключением типов Long и double переменных, которые должны использовать ключевое слово volatile для атомарных операций. @link


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

  1. дважды проверенный запирающий механизм. Часто используется в дизайне Singleton узор. В этом то singleton object needs to be declared volatile.
  2. Ложных Пробуждений. Поток может иногда просыпаться от вызова ожидания, даже если не был выдан вызов notify. Такое поведение называется супуративным пробуждением. Этому можно противопоставить условную переменную (логический флаг). Поставить wait () вызовите цикл while, если флаг true. Поэтому, если поток просыпается от вызова ожидания по каким-либо причинам, кроме notify/notifyall, то он встречает флаг по-прежнему true и, следовательно, вызовы снова ждут. Перед вызовом notify установите этот флаг в true. В этом случае boolean flag is declared as volatile.

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

  1. если у нас есть переменная volatile, она не может быть кэширована в кэш-память компьютера(микропроцессора) любым потоком. Доступ всегда происходил из основной памяти.

  2. если есть запись переход на изменчивую переменную, и вдруг читать запрашивается, гарантируется, что операция записи будет завершена до операции чтения.

два вышеперечисленных качества выводят, что

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

и на других рука,

  • если мы дополнительно исследуем #2 то, что я упомянул, мы можем видеть, что volatile ключевое слово-идеальный способ поддерживать общую переменную, которая имеет ' n ' количество прочитанных потоков и только один поток записи для доступа к нему. Как только мы добавим volatile ключевое слово, это сделано. Никаких других накладных расходов по безопасности резьбы.

Conversly,

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


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

Если вы разрабатываете приложение, которое будет развернуто на сервере приложений (Tomcat, JBoss AS, Glassfish и т. д.), Вам не нужно самостоятельно обрабатывать управление параллелизмом, поскольку оно уже адресовано сервером приложений. Фактически, если я правильно помню, стандарт Java EE запрещает любой контроль параллелизма в сервлетах и EJBs, поскольку он является частью уровня "инфраструктура", который вы должны были освободить от его обработки. Управление параллелизмом в таком приложении выполняется только при реализации одноэлементных объектов. Это даже уже адресовано, если вы связываете свои компоненты с помощью frameworkd, как Spring.

Итак, в большинстве случаев разработки Java, где приложение является веб-приложением и с использованием IOC framework, как Spring или EJB, вам не нужно будет использовать "volatile".


volatile только гарантирует, что все потоки, даже сами, увеличении. Например: счетчик одновременно видит одну и ту же грань переменной. Он не используется вместо синхронизированного или атомарного или другого материала, он полностью синхронизирует чтения. Пожалуйста, не сравнивайте его с другими ключевыми словами java. Как показано в примере ниже volatile переменная операции также атомно-они успешными или сразу.

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

даже вы ставите volatile или не результаты будут всегда отличаться. Но если вы используете AtomicInteger, как показано ниже, результаты всегда будут одинаковыми. То же самое и с synchronized.

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }

Да, я использую его довольно много - это может быть очень полезно для многопоточного кода. Статья, на которую вы указали, хорошая. Хотя есть две важные вещи, чтобы иметь в виду:

  1. вы должны использовать volatile только если вы полностью понять, что он делает и чем это отличается от synchronized. Во многих ситуациях появляется volatile, на поверхности, быть проще больше мощные альтернативы синхронизировано, когда часто лучшее понимание volatile сделало бы четкий что синхронизируется только вариант, который сработает.
  2. volatile фактически не работает в много старых JVMs, хотя синхронизированный делает. Я помню, что видел документ, который ссылался на различные уровни поддержки в разных JVMs, но, к сожалению, я не могу найти его сейчас. Определенно посмотрите на него, если вы используете Java pre 1.5 или если у вас нет контроля над JVMs, на которых будет работать ваша программа.

Абсолютно, да. (И не только на Java, но и на C#.) Бывают случаи, когда вам нужно получить или установить значение, которое гарантированно будет атомарной операцией на вашей данной платформе, например, int или boolean, но не требует накладных расходов на блокировку потока. Ключевое слово volatile позволяет вам убедиться, что при чтении значения вы получаете настоящее значение, а не кэшированное значение, которое было просто устаревшим при записи в другом потоке.


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

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


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

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

предотвращает JVM от чтения значений в регистре и заставляет его значение для чтения из памяти.

A флаг busy используется для предотвращения продолжения потока пока устройство занято и флаг не защищен замком:

while (busy) {
    /* do something else */
}

поток тестирования продолжится, когда другой поток выключит флаг busy:

busy = 0;

однако, поскольку занят часто доступен в потоке тестирования, JVM может оптимизировать тест, поместив значение занят в регистр, а затем проверить содержимое регистра, не читая значение занят в памяти перед каждым тестом. Поток тестирования никогда не будет занят измените, и другой поток изменит только значение busy в памяти, что приведет к взаимоблокировке. Объявление флаг busy как испаряющие силы свое значение быть прочитанным перед каждым испытанием.

уменьшает риск ошибок согласованности памяти.

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

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

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

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

  • чтения и записи являются атомарными для ссылочных переменных и для большинства примитивные переменные (все типы, кроме long и double).
  • чтение и запись являются атомарными для всех переменных, объявленных летучие (включая длинные и двойные переменные).

Ура!


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


из документации oracle страница, потребность в изменчивой переменной возникает для устранения проблем согласованности памяти:

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

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

как поясняется в Peter Parker ответ, в отсутствии volatile модификатор, стек каждого потока может иметь свою собственную копию переменной. Сделав переменную как volatile, исправлены проблемы согласованности памяти.

посмотреть jenkov страница учебника для лучшего понимания.

посмотреть на вопрос, связанный SE Для больше деталей о летучих чехлы и используют летучие:

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

один практический пример использования:

у вас есть много потоков, которые должны печатать текущее время в определенном формате, например:java.text.SimpleDateFormat("HH-mm-ss"). Йон может иметь один класс, который преобразует текущее время в SimpleDateFormat и обновил переменную за каждую секунду. Все остальные потоки могут просто использовать этот volatile переменная для печати текущего времени в файлах журнала.


A переменная Volatile изменяется асинхронно путем одновременного запуска потоков в приложении Java. Не разрешается иметь локальную копию переменной, которая отличается от значения, хранящегося в настоящее время в "основной" памяти. Фактически переменная, объявленная volatile, должна синхронизировать свои данные во всех потоках, чтобы при каждом доступе или обновлении переменной в любом потоке все остальные потоки немедленно видели одно и то же значение. Конечно, вполне вероятно, что летучие переменные имеют более высокие накладные расходы на доступ и обновление, чем "простые" переменные, поскольку потоки могут иметь свою собственную копию данных для повышения эффективности.

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

для справки, обратитесь к этому http://techno-terminal.blogspot.in/2015/11/what-are-volatile-variables.html


ключ volatile при использовании с переменной убедитесь, что потоки, считывающие эту переменную, будут видеть одно и то же значение . Теперь, если у вас есть несколько потоков чтения и записи в переменную, сделать переменную изменчивой будет недостаточно, и данные будут повреждены . Потоки изображений прочитали одно и то же значение , но каждый из них сделал некоторые чаги (скажем, увеличил счетчик), при записи обратно в память нарушается целостность данных . Именно поэтому необходимо сделать varible синхронизировано (возможны различные способы)

Если изменения сделаны 1 потоком, а другим нужно просто прочитать это значение, volatile будет подходящим.


Volatile делает следующее.

1 > Чтение и запись изменчивых переменных различными потоками всегда из памяти, а не из собственного кэша потока или регистра процессора. Таким образом, каждый поток всегда имеет дело с последним значением. 2> Когда 2 разных потока работают с одним и тем же экземпляром или статическими переменными в куче, можно увидеть действия другого как не по порядку. См. блог Джереми Мэнсона об этом. Но волатильность здесь помогает.

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

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

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

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

следующая ссылка github имеет readme, который дает правильное объяснение. https://github.com/sankar4git/volatile_thread_ordering


Мне нравится jenkov это объяснение

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

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

соображения производительности volatile

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