Ключевое слово volatile Java в многопоточности

Я попытался воспроизвести поведение энергонезависимой переменной в многопоточности Java. Вот у меня энергонезависимая переменная test в классе OccurrenceCounter.java. В классе ThreadDemo.java у меня есть метод main, который порождает два потока. В thread1 он постоянно проверяет значение энергонезависимой переменной test. в цикле while. Если я запустил пример ниже в MyRunnableThread1.java, значение occurrenceCounter.isTest() не берется из кеша процессора.

OccurrenceCounter.java:

public class OccurrenceCounter {

    // non-volatile variable
    private   boolean test=true;

    public OccurrenceCounter(boolean test)
    {
        this.test=test;
    } 

    public boolean isTest() {
        return test;
    }

    public void setTest(boolean test) {
        this.test = test;
    }
}

MyRunnableThread1.java:

// Thread 1
public class MyRunnableThread1 implements Runnable {

    private String threadName;
    private OccurrenceCounter occurrenceCounter;

    public MyRunnableThread1(String threadName,OccurrenceCounter occurrenceCounter)
    {
        this.threadName=threadName; 
        this.occurrenceCounter=occurrenceCounter;
    }

    @Override
    public void run() {

        System.out.println("Thread "+threadName + " started");
        System.out.println("value of flag:"+occurrenceCounter.isTest());

        int i=0;
        // random processing code 
        while(i<1000000000L)
        {
           i++; 
           i++;
           i++;
        }
        // checking whether non-volatile variable is taken from cpu cache
        while(occurrenceCounter.isTest())
        {

        }
    System.out.println("Thread "+threadName + " finished");
    }
}

MyRunnableThread2.java:

// Thread 2
public class MyRunnableThread2 implements Runnable {

    private String threadName;
    private OccurrenceCounter occurrenceCounter;

    public MyRunnableThread2(String threadName,OccurrenceCounter occurrenceCounter)
    {
        this.threadName=threadName; 
        this.occurrenceCounter=occurrenceCounter;
    }

    @Override
    public   void run() {

        System.out.println("Thread "+threadName + " started");

        occurrenceCounter.setTest(false);
        System.out.println("Thread "+threadName + " finished");
    }
}

ThreadDemo.java:

public class ThreadDemo {

    public static void main(final String[] arguments) throws InterruptedException {

       System.out.println("main thread started");

       OccurrenceCounter occurrenceCounter =new OccurrenceCounter(true);

       MyRunnableThread1 myRunnableThread1=new MyRunnableThread1("Thread1", occurrenceCounter);
       MyRunnableThread2 myRunnableThread2=new MyRunnableThread2("Thread2", occurrenceCounter);

       Thread t1=new Thread(myRunnableThread1);
       Thread t2=new Thread(myRunnableThread2);

       t1.start();

       try
       {
           Thread.sleep(100);
       }
       catch(Exception e)
       {
           System.out.println("main thread sleep exception:"+e);
       }

       t2.start();

       System.out.println("main thread finished");
   }
}

Здесь в MyRunnableThread1.java в цикле while условие occurrenceCounter.isTest() не возвращается из кэша ЦП, даже если переменная test в классе OcuurenceCounter является энергонезависимой. Но если я удалю первый цикл while:

while(i<1000000000L)
{
   i++; 
   i++;
   i++;
}

Тогда я вижу, что условие occurrenceCounter.isTest() всегда ложно, даже если оно обновляется thread2, а thread1 никогда не завершается. Итак, почему этот цикл while:

while(i<1000000000L)
{
   i++; 
   i++;
   i++;
}

Влияет на поведение энергонезависимой переменной? Это первый цикл while, заставляющий все равно читать значение из памяти, а не из кеша процессора в thread1? Я много пытался получить на это ответ. Но я не мог.

Пожалуйста, помогите мне разобраться в этом.

Возможный дубликат Разница между изменчивым и синхронизированным в Java

rkosegi 25.03.2018 19:50

Без поточно-ориентированного механизма, такого как volatile, synchronized или класса java.util.concurrent, видимость данных между потоками не определена должным образом. Это может быть, а может и не быть видимым. Не полагайтесь на то, что вы наблюдаете; полагаться на то, что гарантировано.

VGR 25.03.2018 20:15
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
180
2

Ответы 2

С volatile JMM может гарантировать, что обновленное значение будет доступно другим потокам.

Без volatile или другой синхронизации обновленное значение мог или не мог будет доступно другим потокам, это сомнительно.

В этом случае петля

while(i<1000000000L) {
    i++; 
    i++;
    i++;
}

Не могу гарантировать видимость в памяти переменной test, это просто совпадение. Не полагайтесь на это.

Привет, user6690200, без этого цикла while он работает нормально, но с этим циклом while он не работает каждый раз. Я пробовал это несколько раз. Это связано с тем, как потоки выполняются в одном ядре процессора. Следовательно, все потоки могут считывать значение из одного и того же обновленного кеша процессора? Пожалуйста, дайте нам информацию об этом.Спасибо

nayak0765 01.04.2018 16:10

Ключевое слово volatile гарантирует, что если один Thread внесет некоторые изменения в эту переменную, то другие Thread, прочитавшие его после изменения, увидят это. Если вы используете простую переменную, не относящуюся к volatile, то другой Threads может ее и не увидеть. Это зависит от внутреннего устройства JVM.

Итак, как это исправить? Есть несколько способов:

  1. Как вы упомянули, вы можете сделать это volatile. Это, наверное, самый простой способ сделать это:

    public class OccurrenceCounter {
    
        private volatile  boolean test=true;
        // ...
    }
    
  2. можно сделать аксессуары synchornized. В этом случае вам необходимо синхронизировать все обращения к переменным:

    public class OccurrenceCounter {
    
        private  boolean test=true;
    
        public OccurrenceCounter(boolean test)
        {
            this.test=test;
        } 
    
        public synchronized boolean isTest() {
            return test;
        }
    
        public synchronized void setTest(boolean test) {
            this.test = test;
        }
    }
    
  3. Вы можете использовать AtomicBoolean, который сделает все это за вас. Цена, которую вы платите за это, состоит в том, что API будет немного более подробным. Вероятно, это излишество, если вы не хотите использовать методы compareAndSet() или getAndSet():

    private AtomicBoolean test = new AtomicBoolean(true);
    
    public OccurrenceCounter(boolean test)
    {
        this.test.set(test);
    } 
    
    public boolean isTest() {
        return test.get();
    }
    
    public void setTest(boolean test) {
        this.test.set(test);
    }
    

Другие вопросы по теме