在 java8 中使用 volatile 实现 Acquire/Release 模型
Implement Acquire/Release model by using volatile in java8
就我而言,我想在 java8 中使用 volatile
实现 Acquire/Release
模型。
所以我编写了使用易失共享变量 I
的代码来保证 MAP
的修改可以被其他线程看到。
public static volatile int I = 0;
public static final Map<String, String> MAP = new HashMap<>();
// run at Thread-1
public static void write(){
MAP.put("test", "test");
I++; // release
}
// run at Thead-2
public static void read(){
int i = I; // acquire
MAP.get("test"); // want to see the modifying by write()
}
我的问题是:
- 代码同步是否正确?
- 会不会是JIT把不用的局部变量
i
去掉了,导致acquire
操作无效?
首先请注意 volatile
变量上的 ++
是 不是原子的 ,因此,在多次更新的情况下你不能依赖它的值.
只要只有一个更新,检查更新是否确实发生就足够了,但执行检查 至关重要。否则,无法保证(应该是 acquire)易失性读取是 subsequent(应该是 release ) 不稳定更新。
只需考虑以下时机:
Thread 1 Thread 2
┌ ┐ [ Read I ]
│ map update │ ┌ ┐
└ ┘ │ map query │
[ Write I ] └ ┘
这里两个线程并发使用map,无可救药地坏掉了,而acquire和release动作没有任何后果,因为获取不是发布的后续。
如果您检查读取的值并且仅当它是另一个线程写入的预期值时才继续,您只能依赖这种关系。
由于整个构造仅适用于单个更新,因此您可以改用 boolean
:
private static volatile boolean I = false;
private static final Map<String, String> MAP = new HashMap<>();
// run at Thread-1
public static void write(){
MAP.put("test", "test");
I = true; // release
}
// run at Thead-2
public static void read(){
if(I) { // acquire
MAP.get("test"); // want to see the modifying by write()
}
}
您不能将此用于多个更新,因为想要执行第二次更新的线程必须确保在第一次更新完成后读取地图的所有线程之前不开始更新地图。但是这种方法根本无法获得这些信息。
在我看来,你混淆了术语。 java 中的 volatile
提供顺序一致性,而 release/acquire
语义则不提供。您可以将其视为 volatile
比 release/acquire
强。您可以阅读,了解两者之间的潜在差异。
然后,文档中有这个subsequent word,意思是:
A write to a volatile field happens-before every subsequent read of that same field.
这意味着 ThreadA
必须 观察 写入 ThreadB
所做的 volatile
字段。这意味着您需要检查是否看到写入:
boolean flag = ...
ThreadA: flag = true; // this is the "release"
ThreadB: if(flag) { .... } // this is the "acquire"
只有当ThreadB
进入if statement
时(也就是获取发生的时候),才能保证在之前发生的所有事情都被写入(flag = true
) 将在 if
子句中可见。
您可以通过使用 VarHandle::setRelease
和 VarHandle::getAcquire
的更便宜的方式实现相同的目的,但要小心,因为它提供的保证不如 volatile
,具体来说它不会 .
所以你的例子有点缺陷。你可以稍微简化你对 release/acquire
的理解,认为 release 是 write 而 reader 必须遵守,特殊规则,例如 volatile
或 VarHandle::setRelease/getAcquire
。这些你都不做。你的I++
(除了不是原子的)没有被其他人观察到,你的int i = I
没有被检查以查看该值是否真的被写入。
就我而言,我想在 java8 中使用 volatile
实现 Acquire/Release
模型。
所以我编写了使用易失共享变量 I
的代码来保证 MAP
的修改可以被其他线程看到。
public static volatile int I = 0;
public static final Map<String, String> MAP = new HashMap<>();
// run at Thread-1
public static void write(){
MAP.put("test", "test");
I++; // release
}
// run at Thead-2
public static void read(){
int i = I; // acquire
MAP.get("test"); // want to see the modifying by write()
}
我的问题是:
- 代码同步是否正确?
- 会不会是JIT把不用的局部变量
i
去掉了,导致acquire
操作无效?
首先请注意 volatile
变量上的 ++
是 不是原子的 ,因此,在多次更新的情况下你不能依赖它的值.
只要只有一个更新,检查更新是否确实发生就足够了,但执行检查 至关重要。否则,无法保证(应该是 acquire)易失性读取是 subsequent(应该是 release ) 不稳定更新。
只需考虑以下时机:
Thread 1 Thread 2
┌ ┐ [ Read I ]
│ map update │ ┌ ┐
└ ┘ │ map query │
[ Write I ] └ ┘
这里两个线程并发使用map,无可救药地坏掉了,而acquire和release动作没有任何后果,因为获取不是发布的后续。
如果您检查读取的值并且仅当它是另一个线程写入的预期值时才继续,您只能依赖这种关系。
由于整个构造仅适用于单个更新,因此您可以改用 boolean
:
private static volatile boolean I = false;
private static final Map<String, String> MAP = new HashMap<>();
// run at Thread-1
public static void write(){
MAP.put("test", "test");
I = true; // release
}
// run at Thead-2
public static void read(){
if(I) { // acquire
MAP.get("test"); // want to see the modifying by write()
}
}
您不能将此用于多个更新,因为想要执行第二次更新的线程必须确保在第一次更新完成后读取地图的所有线程之前不开始更新地图。但是这种方法根本无法获得这些信息。
在我看来,你混淆了术语。 java 中的 volatile
提供顺序一致性,而 release/acquire
语义则不提供。您可以将其视为 volatile
比 release/acquire
强。您可以阅读
然后,文档中有这个subsequent word,意思是:
A write to a volatile field happens-before every subsequent read of that same field.
这意味着 ThreadA
必须 观察 写入 ThreadB
所做的 volatile
字段。这意味着您需要检查是否看到写入:
boolean flag = ...
ThreadA: flag = true; // this is the "release"
ThreadB: if(flag) { .... } // this is the "acquire"
只有当ThreadB
进入if statement
时(也就是获取发生的时候),才能保证在之前发生的所有事情都被写入(flag = true
) 将在 if
子句中可见。
您可以通过使用 VarHandle::setRelease
和 VarHandle::getAcquire
的更便宜的方式实现相同的目的,但要小心,因为它提供的保证不如 volatile
,具体来说它不会
所以你的例子有点缺陷。你可以稍微简化你对 release/acquire
的理解,认为 release 是 write 而 reader 必须遵守,特殊规则,例如 volatile
或 VarHandle::setRelease/getAcquire
。这些你都不做。你的I++
(除了不是原子的)没有被其他人观察到,你的int i = I
没有被检查以查看该值是否真的被写入。