Вопрос

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

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

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

Это было полезно?

Решение

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

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

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

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

Другие советы

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

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

Сообщение в Википедии о Одноэлементный Паттерн показывает изменчивость при использовании.

Важный момент о 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 как not null, и они будут верить, что ему по-прежнему присвоен null.

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

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

Пример использования without 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;  
    }

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

enter image description here

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

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

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

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

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 ключевое слово, обладает двумя основными качествами, которые делают его особенным.

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

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

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

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

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

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

И наоборот,

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

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

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

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

  1. Дважды проверенный запорный механизм.Часто используется в одноэлементном дизайне шаблон.В этом одноэлементный объект должен быть объявлен изменчивым.
  2. Ложные Пробуждения.Поток иногда может прерваться из-за ожидания вызова, даже если не было выдано никакого уведомления.Такое поведение называется избыточным пробуждением.Этому можно противостоять, используя условную переменную (логический флаг).Поместите вызов wait() в цикл while до тех пор, пока флаг имеет значение true.Таким образом, если поток выходит из режима ожидания вызова по каким-либо причинам, отличным от notify / notifyall, то он обнаруживает, что флаг по-прежнему имеет значение true, и, следовательно, снова вызывает wait.Перед вызовом notify установите для этого флага значение true.В этом случае логический флаг объявляется как изменчивый.

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

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

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

volatile только гарантирует, что все потоки, даже сами по себе, увеличиваются.Например:счетчик видит одну и ту же грань переменной в одно и то же время.Он не используется вместо synchronized, атомарного или другого материала, он полностью синхронизирует чтения.Пожалуйста, не сравнивайте его с другими ключевыми словами 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);
        }
    }
}

Даже если вы укажете volatile или not, результаты всегда будут отличаться.Но если вы используете 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 кажется, на первый взгляд, более простой и производительной альтернативой synchronized, хотя часто лучшее понимание volatile сделало бы очевидным, что synchronized - это единственный вариант, который будет работать.
  2. volatile на самом деле не работает в многих старых JVM, хотя synchronized работает.Я помню, что видел документ, в котором упоминались различные уровни поддержки в разных JVM, но, к сожалению, сейчас я не могу его найти.Обязательно изучите это, если вы используете Java до версии 1.5 или если у вас нет контроля над JVM, на которых будет работать ваша программа.

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

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

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

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

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

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

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

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

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

busy = 0;

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

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

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

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

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

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

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

Ваше здоровье!

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

Volatile выполняет следующее.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это расширенная гарантия видимости, так называемая гарантия до того, как это произойдет.

Соображения о производительности энергозависимых

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

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

Ниже приведен очень простой код, демонстрирующий требование volatile для переменной, которая используется для управления выполнением потока из другого потока (это один из сценариев, в котором 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 не используется: ты никогда этого не увидишь 'Остановился на:ХХХ" сообщение даже после "Останавливаясь на:ХХХ', и программа продолжает работать.

Stopping on: 1895303906650500

Когда volatile использованный: вы увидите, как 'Остановился на:ХХХ- немедленно.

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

ДЕМОНСТРАЦИЯ: https://repl.it/repls/SilverAgonizingObjectcode

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top