Какое ключевое слово volatile полезно для - PullRequest
595 голосов
/ 20 сентября 2008

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

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

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

Ответы [ 23 ]

5 голосов
/ 03 марта 2013

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 голосов
/ 20 сентября 2008

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

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

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

3 голосов
/ 11 августа 2014

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

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

2 голосов
/ 31 мая 2018

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

2 голосов
/ 02 марта 2017

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

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

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

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

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

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

busy = 0;

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

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

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

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

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

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

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

Ура!

2 голосов
/ 08 июля 2016

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

1 голос
/ 09 мая 2016

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

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

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

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

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

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

Разница между энергозависимой и синхронизированной в Java

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

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

0 голосов
/ 17 мая 2019
Изменчивая переменная

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

0 голосов
/ 14 февраля 2019

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

Stopping on: 1895303906650500

Когда volatile используется: , вы сразу увидите ' Остановлено: xxx '.

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

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

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