用于线程安全的易失性关键字

时间:2017-07-20 13:36:49

标签: java multithreading thread-safety atomicity

我有以下代码:

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的概念仍然让我感到困惑。提前谢谢。

4 个答案:

答案 0 :(得分:2)

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

仍然可以isPresent()refresh()getName()(如果没有使用外部同步),最后只有isPresent()==truegetName()==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}}。