Я попытался воспроизвести поведение энергонезависимой переменной в многопоточности 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? Я много пытался получить на это ответ. Но я не мог.
Пожалуйста, помогите мне разобраться в этом.
Без поточно-ориентированного механизма, такого как volatile, synchronized или класса java.util.concurrent, видимость данных между потоками не определена должным образом. Это может быть, а может и не быть видимым. Не полагайтесь на то, что вы наблюдаете; полагаться на то, что гарантировано.




С volatile JMM может гарантировать, что обновленное значение будет доступно другим потокам.
Без volatile или другой синхронизации обновленное значение мог или не мог будет доступно другим потокам, это сомнительно.
В этом случае петля
while(i<1000000000L) {
i++;
i++;
i++;
}
Не могу гарантировать видимость в памяти переменной test, это просто совпадение. Не полагайтесь на это.
Привет, user6690200, без этого цикла while он работает нормально, но с этим циклом while он не работает каждый раз. Я пробовал это несколько раз. Это связано с тем, как потоки выполняются в одном ядре процессора. Следовательно, все потоки могут считывать значение из одного и того же обновленного кеша процессора? Пожалуйста, дайте нам информацию об этом.Спасибо
Ключевое слово 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);
}
Возможный дубликат Разница между изменчивым и синхронизированным в Java