使Java易失性工作
我做了一个示例程序来了解易变性是如何工作的。在下面的示例中,即使没有volatile,程序也可以正常工作。有人能帮助我理解程序如何在没有易失性的情况下运行良好吗使Java易失性工作,java,multithreading,Java,Multithreading,我做了一个示例程序来了解易变性是如何工作的。在下面的示例中,即使没有volatile,程序也可以正常工作。有人能帮助我理解程序如何在没有易失性的情况下运行良好吗 public class VolatileExp { private /*volatile*/ boolean statusFlag=false; private void changeState() { try { int counter=0; while
public class VolatileExp {
private /*volatile*/ boolean statusFlag=false;
private void changeState() {
try {
int counter=0;
while (!statusFlag) {
System.err.println("counter: "+counter++);
//Thread.sleep(100);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) {
final VolatileExp hello = new VolatileExp();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
hello.changeState();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
hello.statusFlag=true;
System.err.println("setting the status flag ");
} catch (Exception e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
您无法观察到非volatile变量缺少更新的原因有很多 正如其他人在评论中指出的,你不能指望失败会发生。在这个例子中,您的程序运行得太短,因此优化器在这里不会做出任何努力。使用
-server
选项运行程序将改变这一点
此外,您正在执行一个System.err.println(…)循环内的code>语句,该语句在内部已同步
。因此,除非优化器决定扩大synchronized
代码块以覆盖整个循环(这不太可能,因为这意味着永远保持锁),否则在每次迭代中都会重新读取堆变量。因此,在堆值更改之后,第一个线程迟早会读取更改的标志
因为第二个线程也调用System.err.println(…)
更改标志后,将强制它实际将更新的值写入堆,以便在System.err上隐式地同步两个线程。但即使不进行打印输出,第二个线程最终也会在线程结束时将值写入堆
因此,您有一个程序,由于副作用,可以在大多数系统上运行,但仍然无法正常运行。请注意,理论上,在循环中运行的第一个线程消耗100%的CPU时间,可能会强制第二个线程永远不运行,因此永远不会设置终止标志。然而,今天的大多数系统将在线程之间进行抢先切换
即使它每次都能工作,依赖它也是非常危险的,因为不容易看到它所依赖的副作用,也就是说,在第一个线程中删除print语句并使用-server
选项(或在执行类似优化的任何其他JVM上)运行等简单更改将使程序从意外运行变为可能中断。程序工作正常,但不能保证工作正常。也许十亿分之一的循环是无限循环。这就是多线程很难实现的原因之一。有几件事可以让多线程行为不端:使用-server
,-client
,使用64位和32位版本,向jvm添加非默认优化,删除默认优化,并尝试其他平台。此外,CPU体系结构将在很大程度上影响这一点。Volatile在字段访问之后添加一个写内存屏障,以确保cpu寄存器刷新到缓存中。因此,如果在多CPU(或多核)体系结构中运行此功能,您可能会更快地遇到问题,因为cpu1(可能运行thread1)前的未刷新寄存器对cpu2(可能运行thread2)不可见。但是,@immibis是如何说的,复制它在某种程度上是运气的问题。@immibis,这是真的,但更重要的是它可能永远不会在其他人的JVM中工作。