it-roy-ru.com

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

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

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

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

565
Richard

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

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

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

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

670
Greg Mattes

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

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

Википедия на Singleton Pattern показывает изменчивость в использовании.

156
Ande TURNER

Важный момент относительно 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;
    }
}

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

Если мы не создадим переменную _instancevolatile, то Поток, создающий экземпляр Singleton, не сможет связаться с другим потоком. Таким образом, если поток A создает экземпляр Singleton и сразу после создания процессор испортит и т.д., Все остальные потоки не смогут увидеть значение _instance как ненулевое, и они будут считать, что ему по-прежнему присваивается значение 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:

Отказоустойчивые итераторы обычно реализованы с использованием счетчика volatile объекта списка.

  • Когда список обновляется, счетчик увеличивается. 
  • Когда создается Iterator, текущее значение счетчика внедряется в объект Iterator.
  • Когда выполняется операция Iterator, метод сравнивает два значения счетчика и выдает ConcurrentModificationException, если они различаются.

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

95
Premraj

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

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

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

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
  }
}

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

49
Pyrolistical

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

30
Dave L.

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

12
Donatello Boccaforno

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

12
ykaganovich

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

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

  2. Если операцияwriteвыполняется для переменной типа volatile и внезапно запрашивается операцияread, это гарантирует, что операция записибудет завершена. до операции чтения.

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

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

А с другой стороны,

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

Conversly, 

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

11
Supun Wijerathne

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

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

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

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);
        }
    }
}

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

    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);
            }
        }
    }
5
fatih tekin

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

Если вы разрабатываете приложение, которое будет развернуто на сервере приложений (Tomcat, JBoss AS, Glassfish и т.д.), Вам не нужно обрабатывать управление параллелизмом самостоятельно, поскольку оно уже решено сервером приложений. На самом деле, если я правильно помню, стандарт Java EE запрещает какой-либо контроль параллелизма в сервлетах и ​​EJB-компонентах, поскольку он является частью уровня «инфраструктуры», который, как вы предполагали, был освобожден от его обработки. Вы можете управлять параллелизмом только в таком приложении, если реализуете одноэлементные объекты. Это даже уже решено, если вы связываете свои компоненты, используя frameworkd, как Spring.

Таким образом, в большинстве случаев разработки Java, когда приложение является веб-приложением и использует IoC-фреймворк, такой как Spring или EJB, вам не нужно использовать 'volatile'.

5
Rudi Adianto

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

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

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

3
dgvid

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

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

3
tstuber

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

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

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

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

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

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

busy = 0;

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

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

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

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

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

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

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

Ура!

2
dheeran

Летучий делает следующее.

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

2
sankar banerjee

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

2
Neha Vari

Из документации Oracle page возникает необходимость в энергозависимой переменной для решения проблем с согласованностью памяти:

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

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

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

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

Взгляните на связанный вопрос SE для получения более подробной информации о volatile и вариантах использования volatile:

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

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

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

1
Ravindra babu

Ниже приведен очень простой код, демонстрирующий требование volatile.

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

Когда volatile не используется: вы никогда не увидите сообщение «Stopped on: xxx» даже после «Stopping on: xxx», и программа продолжит работу.

Stopping on: 1895303906650500

Когда используется volatile: вы увидите 'Stopped on: xxx' немедленно.

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300
0
manikanta

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

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

0
Java Main

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

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

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

0
satish

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

0
Niyaz Ahamad

Мне нравится объяснение Дженкова :

Ключевое слово Java volatile используется для пометки переменной Java как «хранящейся в основной памяти». Точнее, это означает, что каждое чтение энергозависимой переменной будет считываться из основной памяти компьютера, а не из кэша ЦП, и что каждая запись в энергозависимую переменную будет записываться в основную память, а не только в кеш ЦП. ,.

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

Это расширенная гарантия видимости, так называемая гарантия «происходит раньше».

Вопросы производительности энергозависимых

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

0
yoAlex5