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

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

Счетчик вхождений.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? Я много пытался получить ответ на это. Но я не мог.

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


person nayak0765    schedule 25.03.2018    source источник
comment
Возможный дубликат Разница между volatile и synchronized в Java   -  person rkosegi    schedule 25.03.2018
comment
Без поточно-ориентированного механизма, такого как volatile, synchronized или класс java.util.concurrent, видимость данных между потоками определена нечетко. Его может быть или не быть видно. Не полагайтесь на то, что вы наблюдаете; полагаться на то, что гарантировано.   -  person VGR    schedule 25.03.2018


Ответы (2)


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

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

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

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

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

person xingbin    schedule 25.03.2018
comment
Привет, пользователь 6690200, Без этого цикла while все работает нормально. Но с этим циклом while каждый раз происходит сбой. Я пробовал это несколько раз. Это связано с тем, что потоки работают в одном и том же ядре процессора. Следовательно, все потоки могут считывать значение из одного и того же обновленного кеша процессора? Пожалуйста, сообщите об этом. Спасибо. - person nayak0765; 01.04.2018

Ключевое слово 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);
    }
    
person Tamas Rev    schedule 26.03.2018