1

如果我对此做坏事,我很抱歉,但我有一个问题是这个问题的衍生问题:

为什么 java 5+ 中的 volatile 不会将缓存的变量副本与主内存同步?

基本上,我想看看当从变量volatile中消除时会发生什么。a这是原始问题的代码,应用了我的修改:

public class Test {
    //volatile static private int a;
    static private int a;
    static private int b;
public static void main(String [] args) throws Exception {
    for (int i = 0; i < 100; i++) {
        new Thread() {

            @Override
            public void run() {
                int tt = b; // makes the jvm cache the value of b

                while (a==0) {

                }
                //some threads never get here (past the a==0 loop)

                if (b == 0) {
                    System.out.println("error");
                }
            }

        }.start();
    }

    b = 1;
    a = 1;
}
}

在我的笔记本电脑(Win 7 64,JVM build 1.7.0_04-b22)上发生的事情是,没有volatile,代码似乎永远运行(运行 20 分钟)。添加更多控制台输出告诉我,虽然 100 个线程中的大多数最终确实看到了afrom 0to的变化1,但总是有一些(少于 10 个)继续a==0循环。

我的问题是:这些线程最终也会看到这种变化吗?如果是的话,与大多数类似线程相比,花费数万倍的时间来完成它是否正常?怎么来的?

4

2 回答 2

7

这根本不是关于变量传播的速度。机器代码可能甚至没有尝试写入内存。该值可以驻留在寄存器中。

无论如何,我的建议是不要探索损坏的 Java 代码的行为:无论如何,它总是会有所不同。相反,学习如何编写正确同步的程序。详细研究 Java 内存模型并了解它的保证。JMM 的保证与支持这些保证的实际 JVM 实现代码相去甚远,并且 JMM 是专门为给 JVM 应用各种优化的自由而编写的。

作为一个特别令人心酸的例子,您的损坏代码的行为可能会在其执行过程中发生变化,因为解释或 C1 代码正在被 C2 代码及时替换。运行您所指的程序没有什么可学的。

于 2013-08-19T11:05:18.243 回答
4

每个线程都有自己的本地内存。从一个线程进行更改,即使是对同一个变量,也可能不会(并且您应该假设:不会)传播到其他线程。

要强制线程之间的内存同步,您必须使用synchronized块并获取该块中的变量值。

实现这一目标的最简单方法是使用Atomic-class 变量,例如AtomicInteger. 它确保原子和数据无竞争地访问它们的值。

使用 volatile 是一种选择,但它会阻止对该变量的任何编译器优化。此外,它只适用于原子操作,你可能会遇到各种竞争条件,使用同步块可以避免。

关于 Java 内存模型如何实际工作的最精确和详细的信息可以在Java 语言规范JSR-133 Java 内存模型和线程规范中找到。这些文件的各种解释(包括我的)可能是错误的,所以当有疑问时记得查看可靠的来源。

于 2013-08-19T11:18:02.437 回答