Следует использовать пример кода, который может доказать «volatile». - PullRequest
15 голосов
/ 28 апреля 2011

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

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

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

Ответы [ 6 ]

15 голосов
/ 28 апреля 2011

Вот пример того, почему volatile необходим.Если вы удалите ключевое слово volatile, поток 1 может никогда не прекратится.(Когда я тестировал Java 1.6 Hotspot в Linux, это действительно было так - ваши результаты могут отличаться, поскольку JVM не обязана выполнять кэширование переменных, не помеченных volatile.)

public class ThreadTest {
  volatile boolean running = true;

  public void test() {
    new Thread(new Runnable() {
      public void run() {
        int counter = 0;
        while (running) {
          counter++;
        }
        System.out.println("Thread 1 finished. Counted up to " + counter);
      }
    }).start();
    new Thread(new Runnable() {
      public void run() {
        // Sleep for a bit so that thread 1 has a chance to start
        try {
          Thread.sleep(100);
        } catch (InterruptedException ignored) { 
         // catch block
        }
        System.out.println("Thread 2 finishing");
        running = false;
      }
    }).start();
  }

  public static void main(String[] args) {
    new ThreadTest().test();
  }
}
4 голосов
/ 29 апреля 2011

Ниже приведен канонический пример необходимости использования volatile (в данном случае для переменной str. Без нее точка доступа отменяет доступ вне цикла (while (str == null)) и run() никогда не завершается. Это произойдетна большинстве JVM-серверов.

public class DelayWrite implements Runnable {
  private String str;
  void setStr(String str) {this.str = str;}

  public void run() {
    while (str == null);
    System.out.println(str);
  }

  public static void main(String[] args) {
    DelayWrite delay = new DelayWrite();
    new Thread(delay).start();
    Thread.sleep(1000);
    delay.setStr("Hello world!!");
  }
}
3 голосов
/ 28 апреля 2011

Эрик, я прочитал ваши комментарии, и один из них меня особенно поразил

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

Очевидная проблема, с которой вы можете столкнуться, - это переупорядочивание компилятора, например, более известное поднятие, как упомянул Саймон Никерсон.Но давайте предположим, что никаких переупорядочений не будет, этот комментарий может быть действительным.

Другая проблема, которую решает volatile, связана с 64-битными переменными (long, double).Если вы записываете в long или double, это рассматривается как два отдельных 32-битных хранилища.Что может случиться с параллельной записью, так это то, что старшие 32 из одного потока записываются в старшие 32 бита регистра, а другой поток записывает младшие 32 бита.Тогда у вас может быть long, который не является ни тем, ни другим.

Кроме того, если вы посмотрите на раздел памяти JLS, вы увидите, что это модель расслабленной памяти.

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

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

2 голосов
/ 28 апреля 2011

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

Они выделяют этот пример:

class Test {
    static volatile int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

Что это означает, что во время one() j никогда не превышает i.Однако другой поток, выполняющий two(), может распечатать значение j, которое намного больше, чем i, потому что, скажем, two() работает и извлекает значение i.Тогда one() работает 1000 раз.Затем поток, выполняющий два, наконец-то снова назначается и получает значение j, которое теперь намного больше значения i.Я думаю, что этот пример прекрасно демонстрирует разницу между volatile и синхронизированными - обновления к i и j являются изменчивыми, что означает, что порядок, в котором они происходят, соответствует исходному коду.Однако эти два обновления происходят отдельно, а не атомарно, поэтому вызывающие абоненты могут видеть значения, которые выглядят (для этого вызывающего абонента), как несовместимые.

В двух словах: Будьте очень осторожны с volatile!

1 голос
/ 31 октября 2017

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

public class VolatileExample {

    private static volatile boolean BOOL = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> { while (BOOL) { } }).start();
        TimeUnit.MILLISECONDS.sleep(500);
        BOOL = false;
    }
}
0 голосов
/ 12 ноября 2018

Чтобы расширить ответ от @ jed-wesley-smith, если вы перетащите его в новый проект, выньте ключевое слово volatile из iterationCount и запустите его, оно никогда не остановится. Добавление ключевого слова volatile в str или iterationCount приведет к успешному завершению кода. Я также заметил, что сон не может быть меньше 5 с использованием Java 8, но, возможно, ваш пробег может отличаться в зависимости от других версий JVM / Java.

public static class DelayWrite implements Runnable
{
    private String str;
    public volatile int iterationCount = 0;

    void setStr(String str)
    {
        this.str = str;
    }

    public void run()
    {
        while (str == null)
        {
            iterationCount++;
        }
        System.out.println(str + " after " + iterationCount + " iterations.");
    }
}

public static void main(String[] args) throws InterruptedException
{
    System.out.println("This should print 'Hello world!' and exit if str or iterationCount is volatile.");
    DelayWrite delay = new DelayWrite();
    new Thread(delay).start();
    Thread.sleep(5);
    System.out.println("Thread sleep gave the thread " + delay.iterationCount + " iterations.");
    delay.setStr("Hello world!!");
}
...