3

我正在阅读 JSR 133 Cookbook 并有以下关于内存屏障的问题。书中有一个插入内存屏障的例子,但只使用了局部变量的写入和读取。假设我有以下变量

int a;
volatile int b;

和代码

b=a;

我是否正确理解这一行会产生以下说明

load a
LoadStore membar
store b
4

1 回答 1

1

JVM 的底层行为仅针对 volatile 变量得到保证。b = a;即使在线程完成对语句的评估之后,两个单独的线程也有可能访问变量“a”的不同值。JVM 只保证对 volatile 变量的访问是序列化的并且具有 Happens-Before 语义。这意味着b = a;在两个不同线程上执行的结果(面对 'a' 的“不稳定”值(哈哈))是不确定的,因为 JVM 只说存储到 'b' 是序列化的,它不保证哪个线程具有优先权。

更准确地说,这意味着 JVM 将变量 'b' 视为拥有自己的锁。一次只允许一个线程读取或写入“b”;这个锁只保护对“b”的访问,没有别的。

现在,这意味着在不同的 JVM 下会发生不同的事情,并且这种锁在不同的机器架构上实际实现的方式可能会导致应用程序的运行时行为大不相同。您应该信任的唯一保证是 Java 参考手册所说的,“一个字段可能被声明为 volatile,在这种情况下,Java 内存模型确保所有线程都看到变量的一致值。” 如需进一步回顾,请参阅 Dennis Byrne 的优秀文章,了解不同 JVM 实现如何处理此问题的一些示例。

Happens-Before 语义在提供的示例中不是很有趣,因为整数原语没有为 volatile 旨在(部分)补救的那种指令重新排序提供太多机会。一个更好的例子是:

private AnObjectWithAComplicatedConstructor _sampleA;
private volatile AnObjectWithAComplicatedConstructor _sampleB;

public void getSampleA() {
    if (_sampleA == null) {
        _sampleA = new AnObjectWithAComplicatedConstructor();
    }
    return _sampleA;
}

public void getSampleB() {
    if (_sampleB == null) {
        _sampleB = new AnObjectWithAComplicatedConstructor();
    }
    return _sampleB;
}

在此示例中,字段“_sampleA”存在严重问题;在多线程情况下,'_sampleA' 很可能正在一个线程中被初始化,同时另一个线程试图使用它,从而导致各种零星且非常非常难以复制的错误。要看到这一点,请考虑线程 X 在 getSampleA() 中执行新的“新”字节码指令语句,然后将(尚未初始化的)结果存储在字段“_sampleA”中。线程 X 现在被 JVM 暂停,线程 Y 开始执行 getSampleA() 并看到“_sampleA”不为空;然后返回哪个未初始化的值,线程 Y 现在开始在结果实例上调用方法,从而导致各种问题;当然,它只会出现在生产中,在奇数时间,

字段 _sampleB 更糟糕的情况是它可能有多个线程初始化单个实例;除了其中一个之外,所有这些最终都将被丢弃。像这样的代码应该包装在一个“同步”块中,但是 volatile 关键字可以解决问题,因为它要求最终存储在 '_sampleB' 中的值具有 Happens-Before 语义,这意味着等号右侧的内容是当等号左边的东西被执行时,保证是完整的。

于 2012-12-21T07:02:44.293 回答