用于线程安全的 volatile 关键字
Volatile keyword for thread safety
我有以下代码:
public class Foo {
private volatile Map<String, String> map;
public Foo() {
refresh();
}
public void refresh() {
map = getData();
}
public boolean isPresent(String id) {
return map.containsKey(id);
}
public String getName(String id) {
return map.get(id);
}
private Map<String, String> getData() {
// logic
}
}
- 上面的代码是线程安全的还是我需要在其中添加
synchronized
或互斥体?如果它不是线程安全的,请说明原因。
此外,我读到应该使用 AtomicReference
而不是这个,但是在 AtomicReference
class 的源代码中,我可以看到该字段用于保存该值是易变的(以及一些方便的方法)。
- 使用
AtomicReference
是否有特定原因?
我已经阅读了多个与此相关的答案,但 volatile 的概念仍然让我感到困惑。提前致谢。
Volatile 仅在这种情况下有用,可以立即更新值。它并不能真正使代码本身成为线程安全的。
但是因为您在评论中声明,您只更新了引用,并且因为引用开关是原子的,所以您的代码将是线程安全的。(来自给定的代码)
如果您不修改 map
的内容(创建时 refresh()
内部除外),则代码中没有可见性问题。
仍然可以执行 isPresent()
、refresh()
、getName()
(如果不使用外部同步)并以 isPresent()==true
和 getName()==null
结束.
如果我正确理解了你的问题和你的评论 - 你的 class Foo
持有一个 Map
其中只有参考应该更新,例如添加了一个全新的 Map
而不是改变它。如果是这个前提:
是否声明volatile
都没有关系。 Java 中的每个 read/write 操作本身都是原子的。您永远不会在这些操作上看到一半的交易。请参阅 JLS 17.7
17.7. Non-Atomic Treatment of double and long
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.
Writes and reads of volatile long and double values are always atomic.
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency's sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.
Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.
编辑:尽管最上面的语句仍然保持原样-为了线程安全,有必要添加 volatile
以反映不同 Threads
的即时更新以反映参考更新。 Thread
的行为是制作它的本地副本,而 volatile
它将执行 happens-before relationship
换句话说 Threads
将具有与 [= 相同的状态11=].
一个 class 是 "thread safe" 如果它在同时被多个线程使用时做正确的事情。除非您能说出 "the right thing" 的含义,尤其是 "the right thing when used by multiple threads" 的含义,否则无法判断 class 是否线程安全。
如果线程 A 调用 foo.isPresent("X")
并且它 returns 为真,然后线程 B 调用 foo.refresh()
,然后线程 A 调用 foo.getName("X")
,正确的事情是什么?
如果您要声明 "thread safety",那么您必须非常明确地说明调用者在这种情况下应该期待什么。
我有以下代码:
public class Foo {
private volatile Map<String, String> map;
public Foo() {
refresh();
}
public void refresh() {
map = getData();
}
public boolean isPresent(String id) {
return map.containsKey(id);
}
public String getName(String id) {
return map.get(id);
}
private Map<String, String> getData() {
// logic
}
}
- 上面的代码是线程安全的还是我需要在其中添加
synchronized
或互斥体?如果它不是线程安全的,请说明原因。
此外,我读到应该使用 AtomicReference
而不是这个,但是在 AtomicReference
class 的源代码中,我可以看到该字段用于保存该值是易变的(以及一些方便的方法)。
- 使用
AtomicReference
是否有特定原因?
我已经阅读了多个与此相关的答案,但 volatile 的概念仍然让我感到困惑。提前致谢。
Volatile 仅在这种情况下有用,可以立即更新值。它并不能真正使代码本身成为线程安全的。
但是因为您在评论中声明,您只更新了引用,并且因为引用开关是原子的,所以您的代码将是线程安全的。(来自给定的代码)
如果您不修改 map
的内容(创建时 refresh()
内部除外),则代码中没有可见性问题。
仍然可以执行 isPresent()
、refresh()
、getName()
(如果不使用外部同步)并以 isPresent()==true
和 getName()==null
结束.
如果我正确理解了你的问题和你的评论 - 你的 class Foo
持有一个 Map
其中只有参考应该更新,例如添加了一个全新的 Map
而不是改变它。如果是这个前提:
是否声明volatile
都没有关系。 Java 中的每个 read/write 操作本身都是原子的。您永远不会在这些操作上看到一半的交易。请参阅 JLS 17.7
17.7. Non-Atomic Treatment of double and long
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.
Writes and reads of volatile long and double values are always atomic.
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency's sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.
Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.
编辑:尽管最上面的语句仍然保持原样-为了线程安全,有必要添加 volatile
以反映不同 Threads
的即时更新以反映参考更新。 Thread
的行为是制作它的本地副本,而 volatile
它将执行 happens-before relationship
换句话说 Threads
将具有与 [= 相同的状态11=].
一个 class 是 "thread safe" 如果它在同时被多个线程使用时做正确的事情。除非您能说出 "the right thing" 的含义,尤其是 "the right thing when used by multiple threads" 的含义,否则无法判断 class 是否线程安全。
如果线程 A 调用 foo.isPresent("X")
并且它 returns 为真,然后线程 B 调用 foo.refresh()
,然后线程 A 调用 foo.getName("X")
,正确的事情是什么?
如果您要声明 "thread safety",那么您必须非常明确地说明调用者在这种情况下应该期待什么。