[英]Do we need to make ConcurrentHashMap volatile?
我们有一个共享的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);
}
}
我们是否需要使映射可变,以便读取器线程尽快看到一个线程的写入?
是否有可能在另一个线程中看不到或很晚看到一个线程中的地图放置?
HashMap
相同问题。
如果你能做到final
那就去做吧。 如果你不能使它成为final
那么是的,你需要使它成为volatile
。 volatile
适用于字段分配,如果它不是final
则有可能(至少根据 JMM)一个线程对 CHM 字段的写入可能对另一个线程不可见。 重申一下,这是ConcurrentHashMap
字段分配,而不是使用 CHM。
话虽如此,你真的应该让它成为final
。
我们是否需要使映射可变,以便读取器线程尽快看到一个线程的写入?
如果您所说的写入是使用 CHM 本身的变异方法(如put
或remove
)完成的,那么您使字段变得易变不会产生影响。 所有内存可见性保证都在 CHM 内完成。
是否有可能在另一个线程中看不到或很晚看到一个线程中的地图放置?
HashMap
相同问题。
不适用于ConcurrentHashMap
。 如果您同时使用普通的HashMap
,请不要。 见: http : //mailinator.blogspot.com/2009/06/beautiful-race-condition.html
volatile
在对相应变量的读取和写入时应用happens-before 语义。
一个字段可以声明为
volatile
,在这种情况下,Java 内存模型确保所有线程看到变量的一致值(第 17.4 节)。
它与变量值引用的对象无关。 你没有修改变量,所以你不应该*有任何问题,除非(*)你没有安全地发布跨线程共享的Test
对象。
正如Lii 在评论中所建议的,假设您没有通过final
、 volatile
或其他一些同步机制采取正确的预防措施,JMM 允许在对象被其构造函数完全初始化之前提供对对象的引用. 因此,您的一个线程可能会在初始化之前尝试使用map
字段(例如,它会看到null
)。 从这个意义上说,代码可能会中断。
是否有可能在另一个线程中看不到或很晚看到一个线程中的地图放置?
这是不可能的,正如javadoc所述, ConcurrentHashMap
方法引入了适当的内存屏障,
检索反映了最近完成的更新操作的结果。 (更正式地,给定键的更新操作与报告更新值的该键的任何(非空)检索具有发生前关系。
但是, HashMap
不是线程安全类型。 volatile
在这里也无济于事,因为它控制对变量的更改,而不是变量引用的对象。 您将需要外部同步来保护put
和get
对HashMap
调用。
这里有 2 个子问题:参考地图的可见性和写入地图的值的可见性。
我们需要制作地图吗...
因此,在您的情况下,您的“地图”参考未正确发布。 这可能会导致 Test.read() 或/和 Test.write() 中的 NullPointerException (这取决于哪个线程实例化 ConcurrentHashMap 并将其放入“map”字段)。 正确的代码将是以下之一:
//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); } }
当然,您可以在 p.1 的情况下使用普通 HashMap(当您使用正确锁定的字段“map”时)而不是 ConcurrentHashMap。 但是,如果您仍然想使用 ConcurrentHashMap 来获得更好的性能,那么正确发布“地图”的最佳方法,如您所见,是使该字段成为最终版本。
这是一篇 Oracle 人员关于安全发布的好文章,顺便说一句: http : //shipilev.net/blog/2014/safe-public-construction/
是否有可能在另一个线程中看不到或很晚看到一个线程中的地图放置?
不,如果您没有获得 NPE(请参阅第 1 页)或正确发布了您的地图,则读者始终会看到作者产生的所有更改,因为一对 ConcurrentHashMap.put/get 会产生适当的内存屏障/Happens-Before边缘。
HashMap 的相同问题
HashMap 根本不是线程安全的。 方法 HashMap.put/get 以非线程安全的方式处理映射的内部状态(非原子,不保证更改状态的线程间可见性),因此,您可能只是破坏了映射的状态。 这意味着您必须使用适当的锁定机制(同步部分、ReadWriteLock 等)来使用 HashMap。 并且,作为锁定的结果,您可以实现您所需要的 - 读者总是可以看到作者产生的所有更改,因为这些锁会产生内存障碍/发生在之前的边缘。
不,你没有。
volatile
意味着变量不能缓存在寄存器中,因此将始终“直写”到内存。 这意味着一个线程对变量的更改将对其他线程可见。
在这种情况下,变量是对 Map 的引用。 您始终使用相同的 Map,因此您不会更改引用 - 而是更改该 Map 的内容。 (也就是说, Map 是可变的。)这也意味着您可以,因此应该final
引用 Map 。
ConcurrentHashMap 与 HashMap 的不同之处在于,您通常可以安全地从不同线程同时读取和写入,无需外部锁定。 但是,如果您希望能够在任何给定点信任大小,执行先检查后写操作等,则需要自己设计。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.