多线程中的Java volatile关键字
Posted
技术标签:
【中文标题】多线程中的Java volatile关键字【英文标题】:Java volatile keyword in multithreading 【发布时间】:2018-09-03 17:17:22 【问题描述】:我试图重现 Java 多线程中的非易失性变量行为。
在这里,我在 OccurrenceCounter.java 类中有非易失性变量 test
。
在 ThreadDemo.java 类中,我有 main
产生两个线程的方法。
在thread1
中它不断检查非易失变量test
的值
在while循环中。
如果我在 MyRunnableThread1.java 中运行下面的示例,occurrenceCounter.isTest()
的值不会从 CPU 缓存中获取。
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 循环中,即使 OcuurenceCounter
类中的 test
变量是非易失性的,CPU 缓存也不会返回条件 occurrenceCounter.isTest()
。
但是如果我删除第一个 while 循环:
while(i<1000000000L)
i++;
i++;
i++;
然后我可以看到条件occurrenceCounter.isTest()
始终为假,即使它由thread2
更新并且thread1
永远不会终止。
那么为什么会出现这个while循环:
while(i<1000000000L)
i++;
i++;
i++;
影响非易失性变量的行为?
这是第一个while循环强制从内存而不是thread1
的CPU缓存中读取值吗?我试图得到这个答案很多。但我做不到。
请任何人帮助我解决这个问题。
【问题讨论】:
Difference between volatile and synchronized in Java的可能重复 如果没有像volatile
、synchronized
或 java.util.concurrent 类这样的线程安全机制,线程之间的数据的可见性就无法很好地定义。它可能可见也可能不可见。不要依赖你所观察到的;依靠保证。
【参考方案1】:
使用volatile
,JMM 可以保证更新后的值可供其他线程使用。
如果没有volatile
或其他同步,更新后的值可能或不能可供其他线程使用,这是不确定的。
在这种情况下,循环
while(i<1000000000L)
i++;
i++;
i++;
无法保证变量test
的内存可见性,纯属巧合。不要依赖它。
【讨论】:
嗨 user6690200,没有这个 while 循环它工作正常。但是有了这个 while 循环它每次都会失败。我多次尝试过。是否由于某些线程在同一 cpu 核心中运行的方式。因此所有线程都能够从同一个更新的 cpu 缓存中读取值?请就此提供您的输入。谢谢【参考方案2】:volatile
关键字确保如果一个Thread
对该变量进行了一些更改,那么在更改后读取它的其他Thread
s 将看到它。如果您使用普通的非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 volatile关键字的主要内容,如果未能解决你的问题,请参考以下文章
Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)
Java 并发编程 -- 并发编程线程基础(线程安全问题可见性问题synchronized / volatile 关键字CASUnsafe指令重排序伪共享Java锁的概述)