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.
Итак, как это исправить? Есть несколько способов:
Как вы упомянули, вы можете сделать это
volatile
, Это, наверное, самый простой способ сделать это:public class OccurrenceCounter { private volatile boolean test=true; // ... }
Вы можете сделать аксессуары
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; } }
Вы можете использовать
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); }