Использование ключевого слова volatile с изменяемым объектом - PullRequest
30 голосов
/ 06 января 2011

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

В примере ниже, работает ли он правильно, если несколько потоков обращаются к volatile Mutable m и изменяют value?

пример

class Mutable {
    private int value;
    public int get()
    {
        return a;
    }
    public int set(int value)
    {
        this.value = value;
    }
}

class Test {
    public volatile Mutable m;
}

Ответы [ 5 ]

17 голосов
/ 07 января 2011

Это своего рода пояснение к некоторым деталям volatile. Написание здесь, потому что это слишком много для комментария. Я хочу привести несколько примеров, которые показывают, как volatile влияет на видимость, и как это изменилось в jdk 1.5.

С учетом следующего примера кода:

public class MyClass
{
  private int _n;
  private volatile int _volN;

  public void setN(int i) {
    _n = i;
  }
  public void setVolN(int i) {
    _volN = i;
  }
  public int getN() { 
    return _n; 
  }
  public int getVolN() { 
    return _volN; 
  }

  public static void main() {
    final MyClass mc = new MyClass();

    Thread t1 = new Thread() {
      public void run() {
        mc.setN(5);
        mc.setVolN(5);
      }
    };

    Thread t2 = new Thread() {
      public void run() {
        int volN = mc.getVolN();
        int n = mc.getN();
        System.out.println("Read: " + volN + ", " + n);
      }
    };

    t1.start();
    t2.start();
  }
}

Поведение этого тестового кода хорошо определено в jdk1.5 +, но не четко определено до jdk1.5.

В мире, предшествующем jdk1.5, не было определенной взаимосвязи между изменчивым доступом и постоянным доступом. следовательно, результат этой программы может быть:

  1. Чтение: 0, 0
  2. Чтение: 0, 5
  3. Чтение: 5, 0
  4. Чтение: 5, 5

В мире jdk1.5 + семантика volatile была изменена, так что volatile-доступ влияет на энергонезависимый доступ точно так же, как синхронизация. поэтому в мире jdk1.5 + возможны только определенные выходы:

  1. Чтение: 0, 0
  2. Чтение: 0, 5
  3. Чтение: 5, 0 <- невозможно </strong>
  4. Чтение: 5, 5

Вывод 3. невозможен, поскольку чтение «5» из энергозависимого _volN устанавливает точку синхронизации между двумя потоками, что означает, что все действия с t1, предпринятые до назначения _volN , должны быть видимыми до t2.

Дальнейшее чтение:

8 голосов
/ 06 января 2011

В вашем примере ключевое слово volatile только гарантирует, что последняя ссылка, записанная любым потоком в 'm', будет видна любому потоку, читающему 'm' впоследствии.

Это не гарантируетчто-нибудь о вашем get () .

Таким образом, используя следующую последовательность:

Thread-1: get()     returns 2
Thread-2: set(3)
Thread-1: get()    

для вас вполне законно вернуться 2, а не 3. volatile ничего не меняет на это.

Но если вы измените свой класс Mutable на этот:

class Mutable {
    private volatile int value;
    public int get()
    {
        return a;
    }
    public int set(int value)
    {
        this.value = value;
    }
}

Тогда гарантируется, что второй get() из Thread-1должен вернуть 3.

Обратите внимание, однако, что volatile обычно не лучший метод синхронизации.

В вашем простом примере get / set (я знаю, это всего лишь пример) класс, подобный AtomicInteger, используя правильную синхронизацию и фактически предоставляя полезные методы, было бы лучше.

5 голосов
/ 06 января 2011

volatile предоставляет только гарантии относительно ссылки на Объект, который объявлен так.Члены этого экземпляра не синхронизируются.

Согласно Википедии , у вас есть:

  • (во всех версиях Java)Существует глобальный порядок чтения и записи в переменную переменной.Это подразумевает, что каждый поток, обращающийся к изменчивому полю, будет читать свое текущее значение перед продолжением, вместо (потенциально) использования кэшированного значения.(Тем не менее, нет никакой гарантии относительно относительного упорядочения энергозависимых операций чтения и записи с обычными операциями чтения и записи, что означает, что это обычно не полезная конструкция потоков.)
  • (в Java 5 или более поздней версии) Volatile чтения и записиустановить отношения «до того, как это произойдет», очень похоже на получение и освобождение мьютекса.

Итак, в основном, вы объявляете поле volatile, взаимодействуя с ним, создавая «точкусинхронизация ", после чего любое изменение будет видно в других потоках.Но после этого использование get() или set() не синхронизируется. Java Spec имеет более подробное объяснение.

0 голосов
/ 06 января 2011

Использование volatile вместо полностью synchronized значения по сути является оптимизацией. Оптимизация происходит из-за более слабых гарантий, предусмотренных для значения volatile по сравнению с доступом synchronized. Преждевременная оптимизация - корень всего зла; в этом случае зло может быть трудно отследить, потому что оно будет в форме расы и тому подобного. Поэтому, если вам нужно спросить, вы, вероятно, не должны его использовать.

0 голосов
/ 06 января 2011

volatile не «обеспечивает видимость».Единственный эффект - предотвращение кэширования переменной процессором, что обеспечивает отношение «происходит до» при одновременном чтении и записи.Он не влияет на члены объекта и не обеспечивает синхронизации synchronized блокировки.

Поскольку вы еще не сказали нам, каково «правильное» поведение вашего кода, на вопрос нельзя ответить.

...