Nik*_*ski 4 java concurrency multithreading volatile
对 volatile 字段的写入和读取分别防止在 volatile 字段之前和之后重新排序读/写。写入 volatile 变量之前的变量读/写不能重新排序在它之后发生,而从 volatile 变量读取之后的读/写不能重新排序在它之前发生。但这项禁令的范围是什么?据我了解, volatile 变量只能防止在使用它的块内重新排序,对吗?
为了清楚起见,让我举一个具体的例子。假设我们有这样的代码:
int i,j,k;
volatile int l;
boolean flag = true;
void someMethod() {
int i = 1;
if (flag) {
j = 2;
}
if (flag) {
k = 3;
l = 4;
}
}
Run Code Online (Sandbox Code Playgroud)
显然,write tol
将阻止 write tok
重新排序,但它会阻止重新排序写入i
和j
关于l
? 换句话说,可以写入i
并j
在写入之后发生l
吗?
更新 1
感谢大家花时间回答我的问题 - 我很感激。问题是你回答了错误的问题。我的问题是关于范围,而不是关于基本概念。问题基本上是编译器在代码中保证“发生在之前”与 volatile 字段的关系有多远。显然编译器可以保证在同一个代码块内,但是封闭块和对等块呢 - 这就是我的问题。@Stephen C 说,volatile 保证发生在整个方法主体内部的行为之前,即使在封闭块中,但我找不到任何确认。他说得对吗,有什么地方可以确认吗?
让我再举一个关于范围界定的具体例子来澄清事情:
setVolatile() {
l = 5;
}
callTheSet() {
i = 6;
setVolatile();
}
Run Code Online (Sandbox Code Playgroud)
i
在这种情况下,编译器会禁止重新排序写入吗?或者编译器不能/没有被编程来跟踪在 volatile 情况下其他方法中发生的事情,并且i
可以重新排序写入之前发生setVolatile()
?或者编译器根本不重新排序方法调用?
我的意思是某处必须有一个点,当编译器将无法跟踪某些代码是否应该在某些 volatile 字段写入之前发生时。否则,一个易失性字段写入/读取可能会影响一半程序的排序,如果不是更多的话。这是一种罕见的情况,但它是可能的。
此外,看看这个报价
在新的内存模型下,volatile 变量不能相互重新排序仍然是正确的。不同之处在于现在不再那么容易对它们周围的正常字段访问重新排序。
“在他们旁边”。这句话暗示,有一个范围 volatile 字段可以防止重新排序。
显然,写入 l 会阻止写入 k 的重新排序,但是它会阻止写入 i 和 j 的重新排序吗?
重新排序的含义并不完全清楚;看我上面的评论。
然而,在Java 5+内存模型,我们可以说,写入i
和j
所发生之前,写入l
将是另一个线程可见后已经阅读l
......只要没有写i
和j
写后l
。
这确实会限制写入i
和的指令的任何重新排序j
。具体来说,它们不能在写入后的内存写入屏障之后移动到l
,因为这可能导致它们对第二个线程不可见。
但这项禁令的范围是什么?
本身没有禁令。
您需要了解指令、重新排序和内存屏障只是实现 Java 内存模型的特定方式的细节。该模型实际上是根据保证在任何“格式良好的执行”中可见的内容来定义的。
据我了解, volatile 会阻止在使用它的块内重新排序,对吗?
实际上,没有。块不考虑。重要的是方法中语句的(程序源代码)顺序。
@Stephen C 说,volatile 保证发生在整个方法主体内部的行为之前,即使在封闭块中,但我找不到任何确认。
确认是 JLS 17.4.3。它声明如下:
在每个线程 t 执行的所有线程间动作中,t 的程序顺序是一个总顺序,它反映了根据 t 的线程内语义执行这些动作的顺序。
如果所有动作以与程序顺序一致的总顺序(执行顺序)发生,则一组动作是顺序一致的,此外,变量 v 的每次读取 r 都会看到写入 w 写入 v 的值,使得:
w 在执行顺序中排在 r 之前,并且
在执行顺序中,没有其他写入 w' 使得 w 在 w' 之前并且 w' 在 r 之前。
顺序一致性是对程序执行中的可见性和顺序的非常有力的保证。在顺序一致的执行中,所有单独的动作(例如读取和写入)都有一个与程序顺序一致的总顺序,每个单独的动作都是原子的,对每个线程都是立即可见的。
如果一个程序没有数据竞争,那么程序的所有执行都将看起来是顺序一致的。
请注意,此定义中没有提及块或范围。
归档时间: |
|
查看次数: |
797 次 |
最近记录: |