Есть ли смысл в использовании летучих долго?


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

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-разрядные чтения будут атомарными, есть ли какая-то точка в когда-нибудь С помощью летучих долго? (т. е. 64-битовый.)

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

3   51   2010-06-14 18:49:32

3 ответа:

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

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

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

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

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

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

  • постоянно переключайте два поля, одно из которых помечено volatile и одно не между всеми битами set и всеми битами clear
  • прочитайте значения полей в другом потоке
  • смотрите, что поле foo (не защищенное volatile) может быть прочитано в несогласованном состоянии, это никогда не происходит с полем бара, защищенным 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-битной виртуальной машине я не мог получить ни одной ошибки за несколько минут.

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

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

есть еще?