[英]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 本身的变异方法(如
put
或remove
)完成的,那么您使字段变得易变不会产生影响。 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 在评论中所建议的,假设您没有通过
final
、 volatile
或其他一些同步机制采取正确的预防措施,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
.您将需要外部同步来保护
put
和get
对HashMap
调用。
There are 2 sub-questions here: visability of reference to a map and visability of values written to the map.这里有 2 个子问题:参考地图的可见性和写入地图的值的可见性。
Do we need to make the map...
我们需要制作地图吗...
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/
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.