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

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

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

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

27 ответов

Решение

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

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

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

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

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

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

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

Важный момент о volatile :

  1. Синхронизация в Java возможна при использовании ключевых слов Java synchronized а также volatile и замки.
  2. На Яве мы не можем иметь 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 создает экземпляр Singleton и сразу после создания процессор испортит и т. Д., Все остальные потоки не смогут увидеть значение _instance как не нуль, и они будут верить, что это все еще назначено нуль.

Почему это происходит? Поскольку потоки считывателя не блокируются, и пока поток записывающего устройства не выйдет из синхронизированного блока, память не будет синхронизирована, и значение _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, становятся видимыми для других потоков только после того, как конструктор объекта полностью завершит свое выполнение.
Источник

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

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

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

Два вышеуказанных качества выводят, что

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

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

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

Conversly,

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

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

volatile => synchronized [Около]

volatileговорит программисту, что значение всегда будет актуальным. Проблема в том, что значение можно сохранить на разных типах аппаратной памяти. Например, это могут быть регистры ЦП, кеш ЦП, ОЗУ... Регистры СPU и кеш ЦП принадлежат ЦП и не могут обмениваться данными, в отличие от ОЗУ, которое находится на помощь в многопоточности

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

Java 5 расширенный volatile поддерживая happens-before [Около]

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

volatileКлючевое слово не лечитrace conditionситуация, когда несколько потоков могут записывать несколько значений одновременно. Ответsynchronizedключевое слово [О]

В результате это безопасно только тогда, когда один поток пишет, а другие просто читаютvolatile ценность

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

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

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

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

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

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

       public class SharedObject {
    public volatile int sharedVariable = 0;
}

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


Пример:

Представьте себе ситуацию, в которой два или более потока имеют доступ к общему объекту, который содержит переменную счетчика, объявленную следующим образом:

       public class SharedObject {
    public int counter = 0;
}

Представьте также, что только поток 1 увеличивает переменную счетчика, но поток 1 и поток 2 могут время от времени читать переменную счетчика.

Если переменная счетчика не объявлена ​​энергозависимой, нет гарантии, когда значение переменной счетчика будет записано из кэша ЦП обратно в основную память. Это означает, что значение переменной счетчика в кэше ЦП может отличаться от значения в основной памяти. Эта ситуация проиллюстрирована здесь:

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

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

1.

ЗАПУСК КОДА БЕЗ ИСПОЛЬЗОВАНИЯ ЛЕТУЧИХ СРЕДСТВ

public class VisibilityDemonstration {

private static int sCount = 0;

public static void main(String[] args) {
    new Consumer().start();
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        return;
    }
    new Producer().start();
}

static class Consumer extends Thread {
    @Override
    public void run() {
        int localValue = -1;
        while (true) {
            if (localValue != sCount) {
                System.out.println("Consumer: detected count change " + sCount);
                localValue = sCount;
            }
            if (sCount >= 5) {
                break;
            }
        }
        System.out.println("Consumer: terminating");
    }
}

static class Producer extends Thread {
    @Override
    public void run() {
        while (sCount < 5) {
            int localValue = sCount;
            localValue++;
            System.out.println("Producer: incrementing count to " + localValue);
            sCount = localValue;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                return;
            }
        }
        System.out.println("Producer: terminating");
    }
}
}

В приведенном выше коде есть два потока - производитель и потребитель.

Производящий поток повторяет цикл 5 раз (с задержкой в ​​1000 миллисекунд или 1 секунду) между ними. На каждой итерации поток-производитель увеличивает значение переменной sCount на 1. Таким образом, производитель изменяет значение sCount с 0 на 5 на всех итерациях.

Потребительский поток находится в постоянном цикле и печатает всякий раз, когда значение sCount изменяется, пока значение не достигнет 5, где оно заканчивается.

Обе петли запускаются одновременно. Таким образом, и производитель, и потребитель должны напечатать значение sCount 5 раз.

ВЫХОД

Consumer: detected count change 0
Producer: incrementing count to 1
Producer: incrementing count to 2
Producer: incrementing count to 3
Producer: incrementing count to 4
Producer: incrementing count to 5
Producer: terminating

АНАЛИЗ

В приведенной выше программе, когда поток-производитель обновляет значение sCount, он обновляет значение переменной в основной памяти (памяти, из которой каждый поток будет первоначально читать значение переменной). Но потребительский поток считывает значение sCount только в первый раз из этой основной памяти, а затем кэширует значение этой переменной в своей собственной памяти. Таким образом, даже если значение исходного sCount в основной памяти было обновлено потоком-производителем, поток-потребитель читает из своего кэшированного значения, которое не обновляется. Это называется ПРОБЛЕМА ВИДИМОСТИ.

2.

ЗАПУСК КОДА С ИСПОЛЬЗОВАНИЕМ НЕПРЕРЫВНЫХ СРЕДСТВ

В приведенном выше коде замените строку кода, в которой объявлено sCount, следующим образом:

private volatile  static int sCount = 0;

ВЫХОД

Consumer: detected count change 0
Producer: incrementing count to 1
Consumer: detected count change 1
Producer: incrementing count to 2
Consumer: detected count change 2
Producer: incrementing count to 3
Consumer: detected count change 3
Producer: incrementing count to 4
Consumer: detected count change 4
Producer: incrementing count to 5
Consumer: detected count change 5
Consumer: terminating
Producer: terminating

АНАЛИЗ

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

Поскольку значение переменной sCount никогда не кэшируется каким-либо потоком, потребитель всегда считывает исходное значение sCount из основной памяти (где оно обновляется потоком-производителем). Итак, в этом случае вывод правильный, когда оба потока выводят разные значения sCount 5 раз.

Таким образом, ключевое слово volatile решает ПРОБЛЕМУ ВИДИМОСТИ.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

busy = 0;

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

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

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

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

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

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

  • Чтение и запись являются атомарными для ссылочных переменных и для большинства примитивных переменных (все типы, кроме long и double).
  • Чтение и запись являются атомарными для всех переменных, объявленных как 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Производительность Соображения изменчивы

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

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

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

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

Переменная 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 не используется: вы никогда не увидите сообщение " Stopped on: xxx " даже после " Stopping on: xxx ", и программа продолжит работу.

Stopping on: 1895303906650500

когда volatile Используется: вы увидите " Остановлено: ххх " немедленно.

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

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

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

Другие вопросы по тегам