简体   繁体   English

线程安全性的可变关键字

[英]Volatile keyword for thread safety

I have the below code: 我有以下代码:

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
  }

}
  • Is the above code thread safe or do I need to add synchronized or mutexes in there? 上面的代码线程安全吗,还是需要在其中添加synchronized或互斥锁? If it's not thread safe, please clarify why. 如果不是线程安全的,请说明原因。

Also, I've read that one should use AtomicReference instead of this, but in the source of the AtomicReference class, I can see that the field used to hold the value is volatile (along with a few convenience methods). 另外,我已经读到应该使用AtomicReference而不是AtomicReference ,但是在AtomicReference类的源代码中,我可以看到用于保存该值的字段是易变的(以及一些便捷方法)。

  • Is there a specific reason to use AtomicReference instead? 是否有使用AtomicReference的特定原因?

I've read multiple answer related to this but the concept of volatile still confuses me. 我已经阅读了与此相关的多个答案,但是volatile的概念仍然使我感到困惑。 Thanks in advance. 提前致谢。

If you're not modifying the contents of map (except inside of refresh() when creating it), then there are no visibility issues in the code. 如果您不修改map的内容(创建时在refresh()内部除外),则代码中没有可见性问题。

It's still possible to do isPresent() , refresh() , getName() (if no outside synchronization is used) and end up with isPresent()==true and getName()==null . 仍然可以执行isPresent()refresh()getName() (如果未使用外部同步的话),并以isPresent()==truegetName()==null

A class is "thread safe" if it does the right thing when it is used by multiple threads at the same time. 如果一个类同时被多个线程使用,那么它在做正确的事情就是“线程安全的”。 There is no way to tell whether a class is thread safe unless you can say what "the right thing" means, and especially, what "the right thing when used by multiple threads" means. 除非您可以说出“正确的事情”的含义,尤其是“多线程使用时正确的事情”的含义,否则无法判断类是否是线程安全的。

What is the right thing if thread A calls foo.isPresent("X") and it returns true, and then thread B calls foo.refresh() , and then thread A calls foo.getName("X") ? 如果线程A调用foo.isPresent("X")并返回true,然后线程B调用foo.refresh() ,然后线程A调用foo.getName("X") ,那是正确的事情呢?

If you are going to claim "thread safety", then you must be very explicit about what the caller should expect in cases like that. 如果要声明“线程安全性”,那么您必须非常明确地了解在这种情况下调用方应该期待什么。

Volatile is only useful in this scenario to update the value immediately. 挥发性仅在这种情况下才有用,可以立即更新值。 It doesn't really make the code by itself thread-safe. 它本身并不能使代码真正成为线程安全的。

But because you've stated in your comment, you only update the reference and because the reference-switch is atomic, your code will be thread-safe.(from the given code) 但是由于您已在注释中声明,因此仅更新了引用,并且由于reference-switch是原子的,因此您的代码将是线程安全的。(来自给定的代码)

If I understood your question correctly and your comments - your class Foo holds a Map in which only the reference should be updated eg a whole new Map added instead of mutating it. 如果我正确理解了您的问题和您的评论-您的Foo类拥有一个Map ,其中仅应更新引用,例如,添加了一个全新的Map而不是对其进行了变异。 If this is the premise: 如果这是前提:

It does not make any difference if you declare it as volatile or not. 如果您将其声明为volatile ,则没有任何区别。 Every read/write operation in Java is atomic itself. Java中的每个读/写操作本身都是原子的。 You will never see a half transaction on these operations. 您将永远不会在这些操作上看到半笔交易。 See the JLS 17.7 参见JLS 17.7

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. 出于Java编程语言内存模型的目的,对非易失性long或double值的单次写入被视为两次单独的写入:一次写入每个32位的一半。 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. 这可能导致线程从一次写入中看到64位值的前32位,而从另一次写入中看到后32位的情况。

Writes and reads of volatile long and double values are always atomic. 易失的long和double值的写入和读取始终是原子的。

Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values. 引用的写入和读取始终是原子的,无论它们是实现为32位还是64位值。

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. 一些实现可能会发现将对64位long或double值的单个写操作划分为对相邻32位值的两个写操作较为方便。 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. Java虚拟机的实现可以自由地原子或两部分地执行对long和double值的写入。

Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. 鼓励Java虚拟机的实现避免在可能的情况下拆分64位值。 Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications. 鼓励程序员将共享的64位值声明为volatile或正确同步其程序,以避免可能的复杂性。

EDIT: Although the top statement still stands as it is - for thread safety it's necessary to add volatile to reflect the immediate update on different Threads to reflect the reference update. 编辑:尽管top语句仍然保持原样-为了线程安全,有必要添加volatile以反映不同Threads上的即时更新以反映引用更新。 The behavior of a Thread is to make local copy of it while with volatile it will do a happens-before relationship in other words the Threads will have the same state of the Map . Thread的行为是对其进行本地复制,而使用volatile它会happens-before relationship ,换言之, Threads将具有与Map相同的状态。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM