我有以下代码:
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
类的源代码中,我可以看到用于保存该值的字段是volatile (以及一些便利方法)。
AtomicReference
的具体原因?我已经阅读了与此相关的多个答案,但是volatile的概念仍然让我感到困惑。提前谢谢。
答案 0 :(得分:2)
如果您没有修改map
的内容(创建时refresh()
内部除外),则代码中没有可见性问题。
仍然可以isPresent()
,refresh()
,getName()
(如果没有使用外部同步),最后只有isPresent()==true
和getName()==null
。< / p>
答案 1 :(得分:2)
如果一个类在多个线程同时使用它时做了正确的事情,那么它就是“线程安全的”。没有办法判断一个类是否是线程安全的,除非你能说出“正确的东西”意味着什么,特别是“当多个线程使用时正确的东西”意味着什么。
如果线程A调用foo.isPresent("X")
并返回true,然后线程B调用foo.refresh()
,然后线程A调用foo.getName("X")
,那么正确的事情是什么?
如果您要声称“线程安全”,那么您必须非常清楚调用者在这种情况下应该期待什么。
答案 2 :(得分:0)
Volatile仅在此方案中用于立即更新值。它并没有真正使代码本身是线程安全的。
但是因为你在评论中已经说过,你只更新引用,因为引用开关是原子的,你的代码将是线程安全的。(来自给定的代码)
答案 3 :(得分:0)
如果我正确理解了您的问题以及您的评论,那么您的课程Foo
会保留Map
,其中只应更新参考资料,例如添加了一个全新的Map
而不是改变它。如果这是前提:
如果您将其声明为volatile
,则没有任何区别。 Java中的每个读/写操作本身都是原子的。您永远不会看到这些操作的一半交易。请参阅JLS 17.7
17.7。双和长的非原子处理
出于Java编程语言内存模型的目的,对非易失性long或double值的单次写入被视为两个单独的写入:每个32位一半写入一次。这可能导致线程从一次写入看到64位值的前32位,而从另一次写入看到第二次32位。
volatile和long值的写入和读取始终是原子的。
写入和读取引用始终是原子的,无论它们是以32位还是64位实现。
某些实现可能会发现将64位长或双值上的单个写操作划分为相邻32位值上的两个写操作很方便。为了效率,这种行为是特定于实现的; Java虚拟机的实现可以原子地或分两部分对long和double值进行写入。
鼓励Java虚拟机的实现避免在可能的情况下拆分64位值。建议程序员将共享的64位值声明为volatile或正确同步其程序以避免可能的复杂情况。
编辑:尽管top语句仍然保持原样 - 为了线程安全,有必要添加volatile
以反映不同Threads
的即时更新以反映参考更新。 Thread
的行为是使用volatile
制作本地副本,它会happens-before relationship
,换句话说Threads
将具有Map
的相同状态1}}。