无法理解Java规范中volatile的示例

hum*_*me1 10 java specifications volatile

我对volatileJava中的含义有了一般的了解.但阅读 Java SE规范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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这允许方法1和方法2同时执行,但是保证对i和j的共享值的访问完全出现次数,并且以完全相同的顺序出现,因为它们似乎在每个执行程序文本期间发生线.因此,j的共享值永远不会大于i的共享值,因为对i的每个更新必须在更新为j之前反映在i的共享值中.但是,对于方法二的任何给定调用都可能会观察到j的值远远大于为i观察到的值,因为方法一可能在方法二获取i的值的时刻之间执行多次.方法二取值j的那一刻.

怎么

j永远不会超过我

,但同时

方法二的任何给定调用都可能观察到j的值远远大于为i观察到的值

??

看起来像矛盾.

ji运行示例程序后更大.为什么要用volatile呢?它没有volatile(也i可能比j规范中先前的一个例子更大)给出几乎相同的结果.为什么这个例子可以替代synchronized

fgb*_*fgb 5

在任何时候,j都不大于i.

这是从什么方法二观察,因为它是访问这些变量的不同ij在不同的时间.i首先访问,然后j稍后访问.

这不是同步版本的直接替代方法,因为行为不同.与不使用volatile的一个区别是,如果没有volatile,则可以始终打印0值.增量不需要是可见的.

该示例演示了volatile访问的顺序.需要这个的例子可能是这样的:

volatile boolean flag = false;
volatile int value;

// Thread 1
if(!flag) {
    value = ...;
    flag = true;
}

// Thread 2
if(flag) {
    System.out.println(value);
    flag = false;
}
Run Code Online (Sandbox Code Playgroud)

和线程2读取线程1设置的值而不是旧值.


Fed*_*sev 3

我认为这个例子的重点是强调使用 volatile 时需要注意并保证顺序;这种行为可能是违反直觉的,示例证明了这一点。

我同意那里的措辞有点晦涩,可以为多种情况提供更明确和清晰的例子,但并不矛盾。

共享价值是同一时刻的价值。如果两个线程在完全相同的时刻读取 i 和 j 的值,则永远不会观察到 j 的值大于 i。易失性保证保持代码中读取和更新的顺序。

然而,在示例中, print+ i+ j是两个不同的操作,间隔任意时间;因此,可以观察到j大于i,因为它可以在读取i之后和读取j之前更新任意次数。

使用 易失性 的要点在于,当您以正确的顺序同时更新和访问易失性变量时,您可以做出原则上在没有易失性的情况下不可能实现的假设。

在上面的示例中,访问顺序two()不允许确定哪个变量更大或相等。

但是,请考虑是否将样本更改为System.out.println("j=" + j + " i=" + i);

在这里,您可以自信地断言 j 的打印值永远不会大于 i 的打印值。如果没有的话,这个假设就不会成立volatile,原因有两个。

首先,更新 i++ 和 j++ 可以由编译器和硬件以任意顺序执行,并且实际上可以作为 j++;i++ 执行。j++如果从其他线程您然后在 之后但之前访问 j 和 i i++,您可以观察,比如说 ,j=1i=0,而不管访问顺序如何。volatile保证不会发生这种情况,并且它将按照源代码中写入的顺序执行操作。

其次,易失性保证另一个线程将看到另一个线程更改的最新值,只要它在上次更新后的稍后时间点访问它。如果没有 volatility,就无法对观测值进行假设。理论上,另一个线程的值可以永远保持为零。该程序可能会打印过去更新的两个零、零和任意数字等;其他线程中观察到的值可能小于更新程序线程在更新后看到的当前值。易失性保证您将在第一个线程更新后在第二个线程中看到该值。

虽然第二个保证可能看起来是第一个保证(顺序保证)的结果,但它们实际上是正交的。

关于synchronized,它允许执行一系列非原子操作,就像i++;j++原子操作一样,例如,如果一个线程执行synchronized,i++;j++另一个线程执行synchronized System.out.println("i=" + i + " j=" + j);,则第一个线程可能不会执行增量序列,而第二个线程则打印,结果将是正确的。

但这是有代价的。首先,synchronized 本身就有性能损失。其次,更重要的是,并不总是需要这样的行为,并且阻塞的线程会浪费时间,从而降低系统吞吐量(例如,您可以在 System.out 期间执行如此多的 i++;j++; )。