Какое ключевое слово volatile полезно для


673

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

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

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

Ответы:


742

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

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

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

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


118
Этот ответ правильный, но неполный. В нем пропущено важное свойство, volatileкоторое появилось в новой модели памяти Java, определенной в JSR 133: когда поток читает volatileпеременную, он видит не только значение, записанное в него последним другим потоком, но также все другие записи в другие переменные, которые были видны в этом другом потоке во время volatileзаписи. Смотрите этот ответ и эту ссылку .
Адам Зальцман

46
Для новичков я бы попросил вас продемонстрировать с помощью некоторого кода (пожалуйста?)
Hungry Blue Dev

6
Статья, связанная в вопросе, имеет примеры кода.
Грег Мэттес

Я думаю, что ссылка «Hennessy & Patterson» не работает. А ссылка на «модель памяти Java» фактически ведет к спецификации языка Java Oracle «Глава 17. Потоки и блокировки».
Крис

2
@fefrei: «немедленно» - разговорный термин. Конечно, это не может быть гарантировано, когда ни время, ни время выполнения, ни алгоритмы планирования потоков фактически не указаны. Единственный способ для программы выяснить, является ли энергозависимое чтение последующим за определенной энергозависимой записью, это проверить, является ли видимое значение ожидаемым записанным.
Хольгер

177

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

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

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


18
Почему там volatileи synchronizedключевые слова?
ptkato

5
С тех пор статья в Википедии, посвященная шаблону синглтона, сильно изменилась и больше не содержит упомянутых volatileпримеров. Его можно найти в архивной версии .
Bskp

1
@ptkato Эти два ключевых слова служат совершенно различным целям, поэтому для сравнения этот вопрос не имеет большого смысла, хотя оба они связаны с параллелизмом. Это все равно что сказать: «Почему есть и то, voidи другое public».
DavidS

134

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

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

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

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

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

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

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


2
«Отказоустойчивые итераторы обычно реализуются с использованием энергозависимого счетчика» - уже не так, слишком дорого: bugs.java.com/bugdatabase/view_bug.do?bug_id=6625725
Всеволод Голованов

безопасны ли двойные проверки для _instance? я думал, что они не безопасны даже с изменчивым
Декстерс

msgstr "который будет инструктировать потоки JVM читать значение изменчивой переменной из основной памяти и не кэшировать ее локально." Хороший вопрос
Хумоюн Ахмад

Для безопасности потока можно пойти private static final Singleton _instance;также.
Chris311

53

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

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

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


2
Интересно, почему это даже необходимо. Разве это не необходимо, если другие потоки должны реагировать на изменение статуса этого потока таким образом, что синхронизация потоков находится под угрозой?
Джори

27
@Jori, вам нужен volatile, потому что поток, читающий close в цикле while, отличается от того, который вызывает close (). Без volatile поток, выполняющий цикл, может никогда не увидеть изменения, чтобы закрыться.
Пиролистический

Вы бы сказали, что есть преимущество между остановкой такого потока или использованием методов Thread # interrupt () и Thread # isInterrupted ()?
Рикардо Бельчиор

2
@Pyrolistical - Вы видели поток, никогда не видя изменений на практике? Или вы можете расширить пример, чтобы надежно вызвать эту проблему? Мне любопытно, потому что я знаю, что использовал (и видел, как другие используют) код, который в основном идентичен примеру, но без volatileключевого слова, и всегда кажется, что он работает нормально.
aroth

2
@aroth: с сегодняшними JVM вы можете заметить, что на практике, даже с простейшими примерами, вы не можете надежно воспроизвести это поведение. В более сложных приложениях у вас иногда есть другие действия с гарантиями видимости памяти в вашем коде, которые заставляют его работать, что особенно опасно, так как вы не знаете, почему это работает, и простое, явно несвязанное изменение в вашем коде может сломать ваш приложение ...
Хольгер

31

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


27

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

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

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

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

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

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

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

Conversly,

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


3
Это объясняет разницу между энергозависимым и синхронизированным.
Аджай

13

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


Чтобы сделать его еще более понятным, не нужно устанавливать логическое значение volatile, потому что чтение и запись логического значения УЖЕ атомарны.
Кай Ван

2
@KaiWang вам не нужно использовать volatile на логических значениях для атомарности. Но вы, конечно, можете по причинам видимости. Ты это хотел сказать?
SusanW

12

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


10

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

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

Весь раздел № 2 выглядит очень запутанным, он объединяет потерянные уведомления, ложные пробуждения и проблемы с видимостью памяти. Также, если все использования флага синхронизированы, то volatile является избыточным. Я думаю, я понимаю вашу точку зрения, но ложное пробуждение не является правильным термином. Просьба уточнить.
Натан Хьюз

5

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

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

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


5

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

4

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

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

4

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

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


3

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


3

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

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

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

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

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

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

busy = 0;

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

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

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

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

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

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

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

Ура!


3

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

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

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

Java 5продлен volatileпутем поддержки happens-before[О]

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

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

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

энергозависимый и синхронизированный


2

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

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


1

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

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

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

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

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

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

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

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

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


1

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


-1

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

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


-1

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


-2

Ниже приведен очень простой код для демонстрации требования 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

Демо: https://repl.it/repls/SilverAgonizingObjectcode


Для downvoter: Уход, чтобы объяснить, почему downvote? Если это не так, по крайней мере, я узнаю, что не так. Я добавил этот комментарий дважды, но не знаю, кто
удаляет

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.