Использование ключевого слова volatile

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

Но в следующей программе отображаются как энергозависимые, так и энергонезависимые переменные.

Измененная переменная не обновляется для второго потока. Может ли кто-нибудь объяснить это, почему testValue не изменен.

class ExampleThread extends Thread {
    private int testValue1;
    private volatile int testValue;
    public ExampleThread(String str){
      super(str);
    }
    public void run() {
    if (getName().equals("Thread 1 "))
    {
        testValue = 10;
        testValue1= 10;
        System.out.println( "Thread 1 testValue1 : " + testValue1);
        System.out.println( "Thread 1 testValue : " + testValue);
    }
    if (getName().equals("Thread 2 "))
    {
        System.out.println( "Thread 2 testValue1 : " + testValue1);
        System.out.println( "Thread 2 testValue : " + testValue);
    }               
}
}

public class VolatileExample {
    public static void main(String args[]) {
        new ExampleThread("Thread 1 ").start();
        new ExampleThread("Thread 2 ").start();
    }
}


output:
Thread 1 testValue1 : 10
Thread 1 testValue : 10
Thread 2 testValue1 : 0
Thread 2 testValue : 0

Ответ 1

Ваши переменные ограничены одним потоком, поэтому нет другого потока, к которому они обращаются. Таким образом, volatile не имеет значения.

Если вы объявили их static, они будут разделяться между различными потоками. Однако даже тогда вы не сможете наблюдать разницу между вашей изменчивой и энергонезависимой переменной. Цитата из Java Concurrency на практике, гл. 3.1.4:

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

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

Однако, поскольку вы печатаете значения переменных из того же потока, которые их модифицировали, вы все равно не увидите никакой разницы.

Update2: Попробуйте эту модифицированную версию (примечание: я ее не тестировал):

class ExampleThread extends Thread {
    private static int testValue1;
    private static volatile int testValue;
    private int newValue;

    public ExampleThread(String str, int newValue){
      super(str);
      this.newValue = newValue;
    }
    public void run() {
      for (int i = 0; i < 10; i++) {
        System.out.println(getName() + " testValue1 before update: " + testValue1);
        System.out.println(getName() + " testValue before update: " + testValue);
        testValue = i * newValue;
        testValue1 = i * newValue;
        System.out.println(getName() + " testValue1 after update: " + testValue1);
        System.out.println(getName() + " testValue after update: " + testValue);
        sleep(10);
      }               
    }               
}

public class VolatileExample {
    public static void main(String args[]) {
        new ExampleThread("Thread 1 ", 5).start();
        new ExampleThread("Thread 2 ", 10).start();
    }
}

Обновление: относительно видимости статических полей - снова из одного и того же файла (раздел 16.2.3):

[...] статически инициализированные объекты не требуют явной синхронизации либо во время построения, либо при ссылке. Однако это относится только к сконструированному состоянию - если объект изменчив, синхронизация по-прежнему требуется как читателям, так и писателям, чтобы сделать последующие изменения видимыми и избежать повреждения данных.

Ответ 2

Это не имеет ничего общего с volatile; это два отдельных экземпляра ExampleThread с их собственными копиями testValue1 и testValue, которые являются полями экземпляра (не static переменными класса, которые являются "разделяемыми" между всеми экземплярами).

Ответ 3

ExampleThread 1 и ExampleThread 2 - разные объекты.

В одном из них вы назначили 10 для обоих полей int и почему вы видите этот вывод для первого потока.

Во вторых вы ничего не присваивали int-полям, поэтому вы получаете 0s.

Ответ 4

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

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

Ответ 5

Может быть, вы потеряли ключевое слово static?

Ответ 6

Вот пример, показывающий переменную, к которой обращаются два потока. В потоке StarterThread задается переменная started при запуске потока. WaiterThread ожидает, что переменная started будет установлена.

public class Main
{
    static /*volatile*/ boolean started = false;

    private static class StarterThread extends Thread
    {
        public void run()
        {
            started = true;
        }
    }

    private static class WaiterThread extends Thread
    {
        public void run()
        {
            while (!started)
            {
            }
        }
    }

    public static void main(String[] args)
    {
        new StarterThread().start();
        new WaiterThread().start();
    }
}

Если started нестабильно, то нет точки синхронизации, которая гарантирует, что WaiterThread когда-либо получит обновленное значение переменной started. Следовательно, поток WaiterThread может потенциально запускаться "бесконечно".