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

Я попытался воспроизвести поведение энергонезависимой переменной в многопоточности Java. Здесь у меня есть энергонезависимая переменная test в классе OccurrenceCounter.java. В классе ThreadDemo.java у меня есть main метод, который порождает две темы. В thread1 он постоянно проверяет значение энергонезависимой переменной testв то время как цикл. Если я запускаю ниже пример в 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 в то время как зациклить условие occurrenceCounter.isTest() не возвращается из кэша процессора, хотя test переменная в OcuurenceCounter класс энергонезависимый. Но если я уберу первый цикл while:

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

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

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

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

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

2 ответа

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

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

В этом случае цикл

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

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

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);
    }
    
Другие вопросы по тегам