используя ключевое слово volatile - PullRequest
4 голосов
/ 15 апреля 2010

Как я понимаю, если мы объявим переменную как 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

Ответы [ 6 ]

7 голосов
/ 15 апреля 2010

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

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

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

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

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

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

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):

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

3 голосов
/ 15 апреля 2010

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

2 голосов
/ 15 апреля 2010

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

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

В секунду вы ничего не присвоили полям int, так что вы получите 0.

1 голос
/ 15 апреля 2010

Вот пример, показывающий переменную, к которой обращаются два потока. Поток 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 потенциально может работать «бесконечно».

1 голос
/ 15 апреля 2010

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

1 голос
/ 15 апреля 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...