使Java易失性工作

使Java易失性工作,java,multithreading,Java,Multithreading,我做了一个示例程序来了解易变性是如何工作的。在下面的示例中,即使没有volatile,程序也可以正常工作。有人能帮助我理解程序如何在没有易失性的情况下运行良好吗 public class VolatileExp { private /*volatile*/ boolean statusFlag=false; private void changeState() { try { int counter=0; while

我做了一个示例程序来了解易变性是如何工作的。在下面的示例中,即使没有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();

    }


}

您无法观察到非volatile变量缺少更新的原因有很多

正如其他人在评论中指出的,你不能指望失败会发生。在这个例子中,您的程序运行得太短,因此优化器在这里不会做出任何努力。使用
-server
选项运行程序将改变这一点

此外,您正在执行一个
System.err.println(…)语句,该语句在内部
已同步
。因此,除非优化器决定扩大
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中工作。