2017-07-20 42 views
1

volatile关键字我有下面的代码:线程安全

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 
    } 

} 
  • 是上面的代码线程安全的或者我需要添加​​或互斥在那里?如果它不是线程安全的,请说明原因。

而且,我读过,应该使用AtomicReference,而不是这个,而是在AtomicReference类的来源,我可以看到,用于保存值的字段是挥发性的(少数方便的方法沿)。

  • 是否有具体原因使用AtomicReference代替?

我读过与此有关的多个答案,但volatile的概念仍然让我困惑。提前致谢。

+0

你应该使用'ConcurrentHashMap'进行实例化。在这种情况下,volatile(或原子引用)在这里是无用的。 – dehasi

+0

它不是线程安全的,虽然对Map类的引用是不稳定的,但数据不是 – nafas

+0

@nafas但我从不修改地图对象本身。我一直在更新参考。 – GurV

回答

2

如果你没有修改map的内容(创建它时refresh()的内部除外),则代码中没有可见性问题。

它仍然可以做到isPresent()refresh()getName()(如果没有外界的同步使用),并与isPresent()==truegetName()==null结束。

+0

你说得对。这可能发生。你认为最好的解决方法是什么?我认为只是使用'getName()'看看它是否为空(我永远不会在名称中为null)。 – GurV

+1

一个选择是将'synchronized'添加到'refresh()',并且每当你做'isPresent()/ getName()'时,你就会在你的'Foo'上同步。但是,由于'getName()'返回'null'(如果不存在),我不确定为什么要编写'isPresent()/ getName()'组合。再说一遍,我不知道你在用你的代码做什么。 – Kayaman

+0

如果我省略'isPresent'方法,我还需要同步吗? – GurV

0

易失性仅在此场景中用于立即更新值。它并不真正使代码本身是线程安全的。

但是,因为你已经在您的评论说,你只更新参考,并因为基准开关是原子,你的代码将是线程安全的。(从给定的代码)

0

如果我正确理解您的问题并提出您的意见 - 您的班级Foo保留Map,其中只有参考值应该更新,例如,添加了全新的Map而不是改变它。如果这是前提条件:

如果您将其声明为volatile,则没有任何区别。 Java中的每个读/写操作都是原子本身。你将永远不会看到这些操作的一半交易。请参阅JLS 17.7

17.7。 Non-Atomic Treatment of double and long

出于Java编程语言内存模型的目的,对非易失性long或double值的单次写入被视为两次单独写入:每次写入32位一半。这可能会导致线程看到来自一次写入的64位值的前32位和来自另一次写入的第二次32位。

对volatile和double值的写入和读取总是原子的。

写入和读取引用始终是原子的,无论它们是以32位还是64位值实现的。

某些实现可能会发现将64位long或double值的单个写入操作划分为相邻32位值的两个写入操作很方便。为了提高效率,此行为是特定于实现的; Java虚拟机的实现可以自由地以原子方式或两部分写入长和双值。

鼓励实施Java虚拟机,以避免在可能的情况下分割64位值。鼓励程序员将共享64位值声明为volatile,或正确同步其程序以避免可能的复杂情况。

编辑:虽然顶部声明仍然有效,因为它是 - 线程安全,有必要增加volatile,以反映不同Threads的即时更新,以反映参考更新。 Thread的行为是使其本地副本,而volatile它将执行happens-before relationship换句话说Threads将具有相同的Map状态。

+0

但是线程的安全性不仅仅是原子性 - 更新应该及时地被其他线程看到,对吧?在这方面不存在不稳定的帮助? – GurV

+0

@GurwinderSingh你是对的。我更多地阅读了内存模型,而引用更新是原子的 - 它不是线程安全的,因为另一个'Thread'可能已经有了它的旧版本,而新版本正在构建。随着波动,有发生之前的关系,这阻止了这一点。 –

2

如果一个类在多个线程同时使用时做了正确的事情,那么这个类就是“线程安全的”。除非您可以说出“正确的东西”的含义,特别是“多线程使用正确的东西”意味着什么,否则无法确定类是否是线程安全的。

如果线程A调用foo.isPresent("X")并返回true,然后线程B调用foo.refresh(),然后线程A调用foo.getName("X"),什么是正确的?

如果你要声明“线程安全”,那么你必须非常明确的调用者应该期望在这种情况下。