如何理解Java语言规范中的易失性示例? [英] how to understand volatile example in Java Language Specification?

查看:77
本文介绍了如何理解Java语言规范中的易失性示例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我认为Java规范中volatile的示例有点错误.

在8.3.1.4中.

class Test {
    static int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

...然后方法二可能偶尔会打印出j的值,该值大于i的值,因为该示例不包含同步,并且在第17.4节中解释的规则下,i和j的共享值可能是更新顺序不正确.

我认为即使这些更新是按顺序进行的,方法2仍可能会看到j大于i,因为System.out.println("i=" + i + " j=" + j)不是原子的,并且i在j之前被读取.

方法二与

相同

read i
read j

所以有可能

read i
i++
j++
read j

在这种情况下,方法二看到j的值大于i,但是更新不会乱序.

所以乱序并不是看到j> i

的唯一原因.

应该是System.out.println("j=" + j + " i=" + i);吗?

这次混乱是看到j> i

的唯一原因

解决方案

这些示例不仅仅是有点错误".

首先,您是正确的,即使在没有重新排序的情况下,在此示例中,j可能会显示为大于i.甚至稍后会在同一示例中确认:

另一种方法是将ij声明为volatile:

class Test {
    static volatile int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

这允许方法one和方法two同时执行,但是保证对ij的共享值的访问发生的次数和次数完全相同,并且顺序完全相同似乎发生在每个线程执行程序文本期间.因此,j的共享值永远不会大于i的共享值,因为对i的每次更新都必须反映在i的共享值中,然后才能进行对j的更新.但是,方法two的任何给定调用都可能会观察到j的值,该值远大于i观察到的值,因为方法one在两次调用之间可能会执行多次.方法two提取i的值以及方法two提取j的时刻.

当然,可以说" j的共享值永远不大于i 的共享值",只是在下一个句子" It可能……[观察] j的值远大于i 的值."

所以j从不大于i,除非观察到它比i大?是否应该说再大一点"是不可能的?

当然不是.该语句没有任何意义,似乎是试图将诸如共享值"与观察值"之类的客观事实区分开来的结果,而实际上,程序中只有可观察到的行为.

这用错误的句子来说明:

这允许方法1和方法2同时执行,但保证对ij的共享值的访问发生的次数与发生在两次出现时的发生次数完全相同,并且顺序也完全相同.每个线程执行程序文本.

即使具有volatile变量,也没有这样的保证. JVM必须保证的是观察到的行为不会与规范相抵触,因此,例如,当您在循环中调用one()千次时,优化器可能仍将其替换为如果可以排除另一个线程见证这种优化的可能性(不是从更高的速度中得出),则原子增量为1000.

或者换句话说,变量(实际上是其存储位置)实际被访问的次数是不可观察的,因此未指定.无论如何都没关系.对于应用程序程序员而言,重要的是,无论变量是否声明为volatilej都可以大于i.

two()中交换ij的读取顺序可能是一个更好的示例,但是我认为,如果JLS§8.3.1.2不尝试解释其含义,那将是最佳选择.口语化了volatile,但只是说它根据 解决方案

The examples are more than "a little wrong".

First, you are right that even without reordering, j may appear greater than i in this example. This is even acknowledged later in the same example:

Another approach would be to declare i and j to be volatile:

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.

Of course, it is abstruse to say "the shared value for j is never greater than that for i", just to say right in the next sentence "It is possible … [to] observe a value for j that is much greater than the value observed for i".

So j is never greater than i, except when it is observed to be much greater than i? Is it supposed to say that "a little greater" is impossible?

Of course not. This statement makes no sense and seems to be the result of trying to separate some objective truth like "the shared value" from "the observed value" whereas in fact, there is only observable behavior in a program.

This is illustrated by the wrong sentence:

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.

Even with volatile variables, there is no such guarantee. All that the JVM must guarantee, is that the observed behavior doesn’t contradict the specification, so when you invoke one() thousand times in a loop, for example, an optimizer may still replace it with an atomic increment by thousand, if it can preclude the possibility of another thread witnessing the presence of such an optimization (other than deducing from the higher speed).

Or in other words, how many times a variable (resp. its memory location) is actually accessed, is not observable and hence, not specified. It doesn’t matter anyway. All that matters to an application programmer, is that j can be greater than i, whether the variables are declared volatile or not.

Swapping the order of the reads of i and j within two() might make it a better example, but I think, it would be best, if JLS §8.3.1.2 did not try to explain the meaning of volatile colloquially, but just stated that it imposes special semantics according to the memory model and left it to the JMM to explain it in a formally correct way.

Programmers are not supposed to master concurrency just by reading 8.3.1.4., so the example is pointless here (in the best case; the worst case would be creating the impression that this example was sufficient to understand the matter).

这篇关于如何理解Java语言规范中的易失性示例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆