Skip to content

令人迷惑的volatile例子(一) #8

Description

@seaswalker

通常情况下关于volatile用法的正确例子是这样:

private volatile boolean flag = true;

@org.junit.Test
public void testVolatile() {
    new Thread(() -> {
        try {
            System.out.println("子线程启动");
            TimeUnit.SECONDS.sleep(3);
            flag = false;
            System.out.println("flag false");
        } catch (InterruptedException ignore) {
        }
    }).start();

    while (flag) {
    }
}

结果也确实如此,不会导致死循环,与之相反如果把volatile去掉:

private boolean flag = true;

的确会死循环,于是我们得出结论:必须使用volatile保证内存可见性。然而事实并不是如此,下面的例子只在while (flag)中加入一句无关痛痒的代码:

private boolean flag = true;

@org.junit.Test
public void testVolatile() {
    new Thread(() -> {
        try {
            System.out.println("子线程启动");
            TimeUnit.SECONDS.sleep(3);
            flag = false;
            System.out.println("flag false");
        } catch (InterruptedException ignore) {
        }
    }).start();

    while (flag) {
        System.out.print(1);
    }
}

你会发现,现在也不会死循环了。
其实,在X86平台上,即使不使用memory barrier处理器也会保证变量修改的全局可见。参考:
Does a memory barrier ensure that the cache coherence has been completed?

上面那个例子死循环的原因其实是JVM编译器优化,将代码实际上变成了(加上volatile或打印阻止了这种优化):

if (flag) {
    while (true) {}
}

参考:请问R大 有没有什么工具可以查看正在运行的类的c/汇编代码

那X86既然已经保证了全局可见,为什么我们还要使用volatile?Java毕竟是跨平台的语言,也许就会运行在不保证全局可见的处理器上,那时就会出错了。
所以如果你没在这种场景下用volatile,并不是你写的对,而是你恰好把代码跑在了X86上而已。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions