我完成了示例程序,以了解易失性工作。在下面的例子中,即使没有volatile,程序也能正常工作。有人可以帮助我理解程序如何在没有易失性的情况下正常工作吗?
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();
}
}
答案 0 :(得分:4)
有几个原因导致您无法观察到非volatile
变量的缺失更新。
正如其他人在评论中指出的那样,你不能依靠失败发生。在这个例子中,你的程序运行得太短,所以优化器不会在这里做任何努力。使用-server
选项运行程序将改变该程序。
此外,您正在循环内执行System.err.println(…);
语句,该语句位于内部synchronized
。因此,堆变量将在每次迭代中重新读取,除非优化器决定放大synchronized
代码块以覆盖整个循环(这是不太可能的,因为这意味着永远持有锁)。因此,在堆值更改之后,第一个线程迟早会读取更改的标志。
由于第二个线程在更改标志后也会调用System.err.println(…);
,因此它将被强制实际将更新的值写入堆中,因此两个线程在synchronized
上隐式System.err
。但即使不进行打印输出,第二个线程最终会在线程结束后将值写入堆中。
因此,您有一个程序可以在大多数系统上运行,因为副作用但仍然存在问题。请注意,理论上,在占用100%CPU时间的循环中运行的第一个线程可能会强制第二个线程永远不会运行,因此永远不会设置终止标志。但是,今天的大多数系统都会抢先在线程之间切换。
即使它每次都有效,依赖它是非常危险的,因为不容易看到它所依赖的副作用,这意味着简单的改变,比如删除第一个线程中的print语句并运行{ {1}}选项(或执行类似优化的任何其他JVM)会使程序意外地运行到可能中断。