Есть ли смысл использовать волатильный лонг? - PullRequest
53 голосов
/ 14 июня 2010

Я иногда использую переменную экземпляра volatile в тех случаях, когда у меня есть два потока, считывающих / записывающих в него и не желающих накладные расходы (или потенциальный риск тупика) при снятии блокировки; например, поток таймера, периодически обновляющий int ID, который предоставляется как метод получения в некотором классе:

public class MyClass {
  private volatile int id;

  public MyClass() {
    ScheduledExecutorService execService = Executors.newScheduledThreadPool(1);
    execService.scheduleAtFixedRate(new Runnable() {
      public void run() {
        ++id;
      }
    }, 0L, 30L, TimeUnit.SECONDS);
  }

  public int getId() {
    return id;
  }
}

Мой вопрос: учитывая, что JLS гарантирует только атомарное 32-битное чтение, есть ли смысл в когда-либо использовании volatile long? (т. е. 64-разрядная версия).

Предостережение : Пожалуйста, не отвечайте, сказав, что использование volatile над synchronized является случаем предварительной оптимизации; Я хорошо знаю, как / когда использовать synchronized, но есть случаи, когда volatile предпочтительнее. Например, при определении bean-компонента Spring для использования в однопоточном приложении я склоняюсь к предпочтению volatile переменных экземпляра, поскольку нет гарантии, что контекст Spring инициализирует свойства каждого bean-компонента в основном потоке.

Ответы [ 3 ]

121 голосов
/ 14 июня 2010

Не уверен, правильно ли я понимаю ваш вопрос, но JLS 8.3.1.4. Летучие поля состояния:

Поле может быть объявлено как volatile, и в этом случае модель памяти Java гарантирует, что все потоки увидят непротиворечивое значение для переменной ( §17.4 ).

и, что еще важнее, JLS 17.7 Неатомарная обработка двойной и длинной :

17,7 Неатомарная обработка двойной и длинной
[...]
Для целей модели памяти языка программирования Java одна запись в энергонезависимое длинное или двойное значение рассматривается как две отдельные записи: по одной на каждую 32-битную половину. Это может привести к ситуации, когда поток видит первые 32 бита 64-битного значения из одной записи и вторые 32 бита из другой записи. Запись и чтение изменчивых длинных и двойных значений всегда атомарны. Запись и чтение ссылок всегда атомарны, независимо от того, реализованы ли они как 32- или 64-битные значения.

То есть переменная «весь» защищена модификатором volatile, а не только двумя частями. Это заставляет меня утверждать, что еще важнее использовать энергозависимость для long с, чем int с, поскольку даже чтение не является атомарным для энергонезависимых длинных /doubles.

9 голосов
/ 23 декабря 2015

Это можно продемонстрировать на примере

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

код

public class VolatileTest {
    private long foo;
    private volatile long bar;
    private static final long A = 0xffffffffffffffffl;
    private static final long B = 0;
    private int clock;
    public VolatileTest() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    foo = clock % 2 == 0 ? A : B;
                    bar = clock % 2 == 0 ? A : B;
                    clock++;
                }
            }

        }).start();
        while (true) {
            long fooRead = foo;
            if (fooRead != A && fooRead != B) {
                System.err.println("foo incomplete write " + Long.toHexString(fooRead));
            }
            long barRead = bar;
            if (barRead != A && barRead != B) {
                System.err.println("bar incomplete write " + Long.toHexString(barRead));
            }
        }
    }

    public static void main(String[] args) {
        new VolatileTest();
    }
}

выход

foo incomplete write ffffffff00000000
foo incomplete write ffffffff00000000
foo incomplete write ffffffff
foo incomplete write ffffffff00000000

Обратите внимание, что это происходит только для меня при работе на 32-битной виртуальной машине, на 64-битной виртуальной машине я не смог получить ни одной ошибки за несколько минут.

4 голосов
/ 23 декабря 2015

"volatile" служит нескольким целям:

  • гарантирует, что атомарные записи удваиваются / увеличиваются
  • гарантирует, что когда поток A видит изменения в volatile-переменной, сделанные потоком B, поток A также может видеть все другие изменения, сделанные потоком B до изменения в volatile-переменной (подумайте об установке числа используемых ячеек в массиве после установки сами клетки).
  • предотвращает оптимизацию компилятора, исходя из предположения, что только один поток может изменить переменную (продуманный цикл while (l != 0) {}.

Есть еще?

...