简体   繁体   English

我们需要让 ConcurrentHashMap 变得易变吗?

[英]Do we need to make ConcurrentHashMap volatile?

We have a shared ConcurrentHashMap which is read and written by 2 threads.我们有一个共享的ConcurrentHashMap ,它由 2 个线程读取和写入。

class Test {
    private ConcurrentHashMap<Object, Object> map = new ConcurrentHashMap<>();

    Object read() {
        return map.get(object);
    }

    void write(Object key, Object object) {
        map.put(key, object);
    }
}

Do we need to make the map volatile so that writes of one thread are seen by the reader threads as soon as possible?我们是否需要使映射可变,以便读取器线程尽快看到一个线程的写入?

Is it possible that a put to the map in one thread is not seen or seen very late by a different thread?是否有可能在另一个线程中看不到或很晚看到一个线程中的地图放置?

Same question for HashMap . HashMap相同问题。

If you can make it final then do that.如果你能做到final那就去做吧。 If you cannot make it final then yes you would need to make it volatile .如果你不能使它成为final那么是的,你需要使它成为volatile volatile applies to the field assignment and if it's not final then there is a possibility (at least per the JMM) that a write of the CHM field by one thread may not be visible to another thread. volatile适用于字段分配,如果它不是final则有可能(至少根据 JMM)一个线程对 CHM 字段的写入可能对另一个线程不可见。 To reiterate, this is the ConcurrentHashMap field assignment and not using the CHM.重申一下,这是ConcurrentHashMap字段分配,而不是使用 CHM。

That being said, you should really make it final .话虽如此,你真的应该让它成为final

Do we need to make the map volatile so that writes of one thread are seen by the reader threads as soon as possible?我们是否需要使映射可变,以便读取器线程尽快看到一个线程的写入?

If the writes you speak of are done using the mutation methods of the CHM itself (like put or remove ) you making the field volatile doesn't have an effect.如果您所说的写入是使用 CHM 本身的变异方法(如putremove )完成的,那么您使字段变得易变不会产生影响。 All memory visibility guarantees are done within the CHM.所有内存可见性保证都在 CHM 内完成。

Is it possible that a put to the map in one thread is not seen or seen very late by a different thread?是否有可能在另一个线程中看不到或很晚看到一个线程中的地图放置? Same question for HashMap . HashMap相同问题。

Not for the ConcurrentHashMap .不适用于ConcurrentHashMap If you are using a plain HashMap concurrently, don't.如果您同时使用普通的HashMap ,请不要。 See: http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html见: http : //mailinator.blogspot.com/2009/06/beautiful-race-condition.html

volatile applies happens-before semantics on reads and writes to the corresponding variable. volatile在对相应变量的读取和写入时应用happens-before 语义。

A field may be declared volatile , in which case the Java Memory Model ensures that all threads see a consistent value for the variable (§17.4).一个字段可以声明为volatile ,在这种情况下,Java 内存模型确保所有线程看到变量的一致值(第 17.4 节)。

It has nothing to do with objects referenced by the variable's value.它与变量值引用的对象无关。 You're not modifying the variable, so you shouldn't * have any problems, unless(*) you're not safely publishing the Test object that is shared across threads.你没有修改变量,所以你不应该*有任何问题,除非(*)你没有安全地发布跨线程共享的Test对象。

As Lii suggests in the coments , assuming you don't take the right precautions, through final , volatile , or some other synchronization mechanism, the JMM allows a reference to an object to be made available before the object has been fully initialized by its constructor.正如Lii 在评论中所建议的,假设您没有通过finalvolatile或其他一些同步机制采取正确的预防措施,JMM 允许在对象被其构造函数完全初始化之前提供对对象的引用. As such, one of your threads could try to use the map field before it's been initialized (eg. it would see null ).因此,您的一个线程可能会在初始化之前尝试使用map字段(例如,它会看到null )。 In that sense, the code could break.从这个意义上说,代码可能会中断。

Is it possible that a put to the map in one thread is not seen or seen very late by a different thread?是否有可能在另一个线程中看不到或很晚看到一个线程中的地图放置?

This is not possible, as the javadoc states, ConcurrentHashMap methods introduce appropriate memory barriers,这是不可能的,正如javadoc所述, ConcurrentHashMap方法引入了适当的内存屏障,

Retrievals reflect the results of the most recently completed update operations holding upon their onset.检索反映了最近完成的更新操作的结果。 (More formally, an update operation for a given key bears a happens-before relation with any (non-null) retrieval for that key reporting the updated value. (更正式地,给定键的更新操作与报告更新值的该键的任何(非空)检索具有发生前关系。

HashMap , however, is not a thread-safe type.但是, HashMap不是线程安全类型。 volatile wouldn't help here either because it controls changes to the variable, not the object referenced by the variable. volatile在这里也无济于事,因为它控制对变量的更改,而不是变量引用的对象。 You'll need external synchronization to protect put and get calls to a HashMap .您将需要外部同步来保护putgetHashMap调用。

There are 2 sub-questions here: visability of reference to a map and visability of values written to the map.这里有 2 个子问题:参考地图的可见性和写入地图的值的可见性

  1. Visability of the reference to the map:参考地图的可见性:

    Do we need to make the map...我们需要制作地图吗...

You should care about safe publication of your references in multi-threading environment. 应该关心在多线程环境中安全发布您的参考文献。 Safe publication means all the values written before the publication are visible to all readers that observe the published object. 安全发布意味着在发布之前写入的所有值对于观察发布对象的所有读者都是可见的。 There are the following few ways to publish a reference safely according to JMM: 根据JMM,有以下几种方法可以安全地发布参考:

  1. Provide access to the reference through a properly locked field (JLS 17.4.5)通过正确锁定的字段提供对参考的访问 (JLS 17.4.5)
  2. Use static initializer to do the initializing stores (JLS 12.4) ( not our case actually )使用静态初始化程序进行初始化存储(JLS 12.4)(实际上不是我们的情况
  3. Provide access to the reference via a volatile field (JLS 17.4.5), or as the consequence of this rule, via the AtomicX classes like AtomicReference通过 volatile 字段 (JLS 17.4.5) 或作为此规则的结果,通过 AtomicX 类(如 AtomicReference)提供对引用的访问
  4. Initialize the value as a final field (JLS 17.5)将值初始化为最终字段 (JLS 17.5)

So, in your case your "map" reference isn't published correctly.因此,在您的情况下,您的“地图”参考未正确发布。 This may cause NullPointerException in Test.read() or/and Test.write() (it depends on which thread instantiates ConcurrentHashMap and puts it into "map" field).这可能会导致 Test.read() 或/和 Test.write() 中的 NullPointerException (这取决于哪个线程实例化 ConcurrentHashMap 并将其放入“map”字段)。 Correct code would be one of the following:正确的代码将是以下之一:

 //1. Provide access to the reference through a properly locked field class Test { ConcurrentHashMap map; synchronized void init(ConcurrentHashMap map) { this.map = map; } synchronized void read() { map.get(object); } synchronized void write() { map.put(key, object); } } // or class Test { ReadWriteLock rwl = new ReentrantReadWriteLock(); ConcurrentHashMap map; void init(ConcurrentHashMap map) { rwl.writeLock().lock(); this.map = map; rwl.writeLock().release(); } void read() { rwl.readLock().lock(); try { map.get(object); } finally { rwl.readLock().release(); } } void write() { rwl.writeLock().lock(); try { map.put(key, object); } finally { rwl.writeLock().release(); } } } // 3. Provide access to the reference via a volatile field class Test { volatile ConcurrentHashMap map; // or AtomicReference<ConcurrentHashMap> map = new AtomicReference(); void init(ConcurrentHashMap map) { this.map = map; } void read() { map.get(object); } void write() { map.put(key, object); } } // 4. Initialize the value as a final field class Test { final ConcurrentHashMap map; Test(ConcurrentHashMap map) { this.map = map; } void read() { map.get(object); } void write() { map.put(key, object); } }

Of course, you can use plain HashMap in case of p.1 (when you work with the properly locked field "map") instead of ConcurrentHashMap.当然,您可以在 p.1 的情况下使用普通 HashMap(当您使用正确锁定的字段“map”时)而不是 ConcurrentHashMap。 But if you still want to use ConcurrentHashMap for better performance, the best way to publish your "map" correctly, as you see, is to make the field final.但是,如果您仍然想使用 ConcurrentHashMap 来获得更好的性能,那么正确发布“地图”的最佳方法,如您所见,是使该字段成为最终版本。

Here is a nice article about safe publication from an Oracle guy, btw: http://shipilev.net/blog/2014/safe-public-construction/这是一篇 Oracle 人员关于安全发布的好文章,顺便说一句: http : //shipilev.net/blog/2014/safe-public-construction/

  1. Visability of values written to the map:写入地图的值的可见性:

Is it possible that a put to the map in one thread is not seen or seen very late by a different thread?是否有可能在另一个线程中看不到或很晚看到一个线程中的地图放置?

No, if you don't get NPE (see p.1) or have published your map correctly, a reader always sees all changes produced by a writer, because a pair of ConcurrentHashMap.put/get produces appropriate memory barriers/Happens-Before edge.不,如果您没有获得 NPE(请参阅第 1 页)或正确发布了您的地图,则读者始终会看到作者产生的所有更改,因为一对 ConcurrentHashMap.put/get 会产生适当的内存屏障/Happens-Before边缘。

Same question for HashMap HashMap 的相同问题

HashMap isn't thread safe at all. HashMap 根本不是线程安全的。 Methods HashMap.put/get work with internal state of the map in not thread-safe manner (non-atomic, no inter-thread visibility of changed state guaranteed), so, you may just corrupt state of the map.方法 HashMap.put/get 以非线程安全的方式处理映射的内部状态(非原子,不保证更改状态的线程间可见性),因此,您可能只是破坏了映射的状态。 This means you must use an appropriate locking mechanism (synchronized sections, ReadWriteLock etc.) to work with HashMap.这意味着您必须使用适当的锁定机制(同步部分、ReadWriteLock 等)来使用 HashMap。 And, as result of locking, you achieve what you need - a reader always sees all changes produced by a writer because those locks produce memory barriers/Happens-Before edges.并且,作为锁定的结果,您可以实现您所需要的 - 读者总是可以看到作者产生的所有更改,因为这些锁会产生内存障碍/发生在之前的边缘。

No, you don't.不,你没有。

volatile means that the variable cannot be cached in a register, and so will always be "write-through" to memory. volatile意味着变量不能缓存在寄存器中,因此将始终“直写”到内存。 This means that one thread's change to a variable will be visible to other threads.这意味着一个线程对变量的更改将对其他线程可见。

In this case, the variable is a reference to a Map.在这种情况下,变量是对 Map 的引用 You use the same Map all the time, so you don't change the reference - rather you change the contents of that Map.您始终使用相同的 Map,因此您不会更改引用 - 而是更改该 Map 的内容。 (This is to say, the Map is mutable .) This also means that you can, and therefore should, make the reference to the Map final . (也就是说, Map 是可变的。)这也意味着您可以,因此应该final引用 Map 。

The ConcurrentHashMap differs from the HashMap in that you can typically safely read from it and write to it at the same time from different threads, without external locking. ConcurrentHashMap 与 HashMap 的不同之处在于,您通常可以安全地从不同线程同时读取写入,无需外部锁定。 If you, however, want to be able to trust the size at any given point, do check-then-write operations or the like, you need to design that yourself.但是,如果您希望能够在任何给定点信任大小,执行先检查后写操作等,则需要自己设计。

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

相关问题 我们是否需要Hashtable,因为我们有ConcurrentHashMap? - Do we need Hashtable since we have ConcurrentHashMap? 为什么我们需要锁定 Hashtable/ConcurrentHashMap 的 put 方法? - Why do we need lock in the put methods of Hashtable/ConcurrentHashMap? 当数组变量是volatile时,我们是否需要同步对数组的访问? - Do we need to synchronize access to an array when the array variable is volatile? Java中的挥发性与原子性,为什么我们需要原子工具包 - Volatile vs Atomic in Java, why do we need atomic toolkit 在读取器/写入器场景中,是否需要使两个变量都可变? - Do I need to make both variables volatile in reader/writer scenario? 为什么我们需要Scala的ParHashMap,而Java却有ConcurrentHashMap - Why do we need for ParHashMap from Scala while there is ConcurrentHashMap from Java java - ConcurrentHashMap 中的易失语义 - java - volatile semantics in ConcurrentHashMap 易失性HashMap与ConcurrentHashMap - Volatile HashMap vs ConcurrentHashMap 我们是否需要创建一个字段&#39;volatile&#39;,如果 Thread1 进入同步块,更新它,仍然在同步块内,线程 2 在同步之外读取字段? - Do we need to make a field 'volatile', if Thread1 enters sync block, updates it, is still inside the sync block, Thread2 outside of sync reads field? 我需要使用易失性吗? - Do I need to use volatile?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM