Skip to content

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

Open
@seaswalker

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上而已。

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