Java SE Specification 8.3.1.4我有一个问题,了解一些挥发性的例子下面的文字.
class Test { static volatile int i = 0,j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } }
This allows method one and method two to be executed concurrently,but
guarantees that accesses to the shared values for i and j occur
exactly as many times,and in exactly the same order,as they appear
to occur during execution of the program text by each thread.
Therefore,the shared value for j is never greater than that for i,
because each update to i must be reflected in the shared value for i
before the update to j occurs. It is possible,however,that any given
invocation of method two might observe a value for j that is much
greater than the value observed for i,because method one might be
executed many times between the moment when method two fetches the
value of i and the moment when method two fetches the value of j.
怎么
j never greater than i
,但同时
any given invocation of method two might observe a value for j that is
much greater than the value observed for i
??
看起来像矛盾.
运行示例程序后,我的j比我大.为什么使用挥发性呢?它给出几乎相同的结果没有挥发性(我也可以大于j,前面的规范之一).为什么这个例子在这里作为同步的替代方法?
解决方法
我同意这个措词有点模糊,有可能为多个案例提供更明确和明确的例子,但没有矛盾.
共享值是同一时刻的值.如果两个线程在完全相同的时刻读取i和j的值,则j的值将不会被观察到大于i.易于保证在代码中保持读取和更新顺序.
然而,在样本中,打印i和j是两个不同的操作,间隔任意时间;因此,可以观察到j大于i,因为可以在读取i之后和读取j之前任意次数更新.
使用volatile的一点是,当您以正确的顺序同时更新和访问volatile变量时,您可以假设原则上不可能发生波动.
在上面的示例中,two()中的访问顺序不允许以哪个变量大于或等于的置信度得出结论.
但是,如果将样本更改为System.out.println(“j =”j“i =”i);
在这里,您可以断定j的打印值不会大于i的打印值.由于两个原因,这种假设不会有波动.
首先,更新i和j可以由编译器和硬件以任意顺序执行,实际上可以以j执行; i.如果来自其他线程,那么您可以在j之后访问j和i,但在我之前,您可以观察到j = 1和i = 0,无论访问顺序如何. volatile保证不会发生这种情况,它将按照您的源代码中的顺序执行操作.
其次,volatile保证另一个线程会看到最近的值被另一个线程改变,只要它在最后一次更新后的较晚时间内访问它.没有波动,对于观测值不能有假设.在理论上,值可以永远保持在另一个线程零点.该程序可以从过去的更新中打印两个零,零和任意数字等;其他线程中的观察值可能会小于更新器线程在更新后看到的当前值.在第一个更新之后,volatile将保证在第二个线程中看到值.
虽然第二个保证似乎是第一个(订单保证)的结果,但实际上它们是正交的.
关于同步,它允许执行非原子操作的序列,例如i; j作为原子操作,例如如果一个线程同步i; j,另一个线程同步System.out.println(“i =”i“j =”j);第一个线程可能不执行增量序列,而第二个打印,结果将是正确的.
但这是一个代价.首先,synhronized本身具有性能损失.第二,更重要的是,并不总是需要这样的行为,并且阻塞的线程浪费时间,降低了系统吞吐量(例如,您可以在System.out中执行许多i; j;).