提问人:asimes 提问时间:10/10/2018 更新时间:10/11/2018 访问量:782
Java 易失性循环
Java volatile loop
问:
我正在处理某人的代码,并遇到了与此相当的内容:
for (int i = 0; i < someVolatileMember; i++) {
// Removed for SO
}
其中定义如下:someVolatileMember
private volatile int someVolatileMember;
如果某个线程 A 正在运行 for 循环,而另一个线程 B 正在写入,那么我假设当线程 A 运行循环时要执行的迭代次数会发生变化,这不是很好。我认为这将解决它:someVolatileMember
final int someLocalVar = someVolatileMember;
for (int i = 0; i < someLocalVar; i++) {
// Removed for SO
}
我的问题是:
- 只是为了确认线程 A 所做的迭代次数可以
如果线程 B 修改,则当 for 循环处于活动状态时更改
someVolatileMember
- 本地非易失性副本足以确保当 线程 A 运行循环,线程 B 无法更改 迭 代
答:
你的理解是正确的:
根据 Java 语言规范,字段的语义可确保在不同线程之间完成更新后看到的值之间的一致性:
volatile
Java 编程语言提供了第二种机制,即字段,它在某些目的上比锁定更方便。
volatile
可以声明一个字段,在这种情况下,Java 内存模型确保所有线程都看到变量的一致值 (§17.4)。
volatile
请注意,即使没有修饰符,循环计数也可能根据许多因素而变化。
volatile
分配变量后,其值永远不会更改,因此循环计数不会更改。
final
评论
private int someMember
someMember
volatile
此外,还会对性能产生影响,因为 的值永远不会从最本地的缓存中获取,但 CPU 会采取其他步骤来确保修改得到传播(可能是缓存一致性协议、延迟读取到 L3 缓存或从 RAM 读取)。使用变量的范围内的其他变量也有影响(这些变量也与主内存同步,但我在这里不演示)。volatile
volatile
关于性能,代码如下:
private static volatile int limit = 1_000_000_000;
public static void main(String[] args) {
long start = System.nanoTime();
for (int i = 0; i < limit; i++ ) {
limit--; //modifying and reading, otherwise compiler will optimise volatile out
}
System.out.println(limit + " took " + (System.nanoTime() - start) / 1_000_000 + "ms");
}
...指纹500000000 took 4384ms
从上面删除关键字将导致输出。volatile
500000000 took 275ms
评论
volatile
volatile
volatile
好吧,首先,该字段是私有的(除非您省略了一些实际上可能会更改它的方法)......
这个循环有点无意义,它的编写方式和假设存在实际上可能会改变的方法;之所以如此,是因为您可能永远不知道何时完成,或者是否完成。这甚至可能是一个成本更高的循环,因为具有非易失性字段,因为这意味着在 CPU 级别使缓存失效和耗尽缓冲区比通常的变量要频繁得多。someVolatileMember
volatile
您的解决方案首先读取易失性并使用它实际上是一种非常常见的模式;这也催生了一种非常常见的反模式:“先检查后行动”......你把它读入一个局部变量,因为如果它以后发生了变化,你不在乎 - 你正在使用你目前拥有的最新副本。所以是的,您在本地复制它的解决方案很好。
评论