在这里阅读:
没有volatile
就说
“然后方法二偶尔可以打印
j
的值大于i
的值,因为该示例不包含同步和”
class Test {
static volatile int i = 0, j = 0;
static void one() { i++; j++; }
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
用volatile表示
“但是,对于方法二的任何给定调用都可能会观察到
j
的值远远大于i
观察到的值,因为方法一可能会执行多次在方法二取值i
的时刻与方法二取值j
的时刻之间。“
在行为'正确'与同步化,但我很困惑volatile
带来什么好处?
我认为volatile
gaurantees保留了顺序,所以我会想到在某些情况下,i的值可能大于j
,但不是相反,因为这意味着顺序增量改变了。
这是文档中的拼写错误吗?如果没有,请在使用j
时说明i
如何大于volatile
。
答案 0 :(得分:2)
据说在方法二中间方法可以运行几个,并且为j读取的值将高于为i读取的值。
read i run method 1 run method 1 read j
答案 1 :(得分:1)
volatile
变量告诉JIT编译器不要执行任何可能影响对该变量的访问顺序的优化。对volatile变量的写入总是在内存中执行,而不是在高速缓存或cpu寄存器上执行。
还有两点:
答案 2 :(得分:1)
根据我的理解,volatile保证不同的线程将引用相同的变量而不是复制它,即,如果更新线程中的volatile变量,所有其他线程将更新此变量,因为它们都引用相同的变量。可以在Why Volatile Matters找到一个很好的例子。
关于方法二的事情是它不是Atomic。它不会仅在一个CPU周期中运行。您可以在@Sign声明的不同操作中进行划分。即使i ++不是原子的,因为它需要读取变量i,增加它并将其再次存储在内存中的i引用中。
答案 3 :(得分:1)
你有挥发性的权利;你只是没有仔细阅读你引用的内容:
因为方法一可能会在一瞬间执行多次 当方法二取出i的值和方法二的时刻 获取j的值
订单保留在one()
中,即two()
中,i
被提取并打印,但在打印i
时i
j
通过从其他线程调用one()
,可能会多次递增j
,因此i
的打印值将高于{{1}}的打印值。< / p>
答案 4 :(得分:0)
volatile使读取或写入线程安全/内存一致。然而,它不会使读取和写入原子。如果只有一个线程会更新它,那么使用volatile
就可以了。
我建议您使用AtomicInteger。
答案 5 :(得分:0)
在“源代码顺序”中,增加到i
发生在增量为j
之前。因此,线程调用方法one()
将始终遵守i >= j
。但是,当其他线程观察到这些变量时,他们可以看到不同的东西。
某些事件确定了JLS称之为“同步顺序”的内容。将这些事件(以及仅这些事件)称为“在其他人之前发生”是有意义的。写入易失性变量就是其中之一。在不使用volatile
的情况下,在 i
之前说j
递增没有任何意义;这些写入可以重新排序,其他线程可以观察到重新排序。
没有volatile
可能发生的事情的一个更好的例子是:
static void oneAndAHalf() { System.out.println("j=" + j + " i=" + i);
即使j
出现, i
后递增, 之前 > j
,您仍然可以观察i
,因为删除j > i
会允许对volatile
中的操作进行重新排序。添加one()
,volatile
将始终显示oneAndAHalf()
,正如您所期望的那样。
如果您带走i >= j
,则方法volatile
可以打印two()
的值大于j
,原因有两个:因为操作已重新排序,或因为i
和i
未被原子化处理。当前的j
方法并未明确说明two()
的效用。添加volatile
,您将获得相同的输出,但唯一的原因是操作不是原子操作。
要查看一致的视图,volatile
,两种方法都可以i == j
。这将使两个变量的增量看起来都是原子的。