我们有一个共享的ConcurrentHashMap,由2个线程读取和写入。
class Test {
ConcurrentHashMap map;
read() {
map.get(object);
}
write() {
map.put(key, object);
}
}
我们是否需要使地图变得易变,以便读者线程尽快看到一个线程的写入?
是否有可能在一个线程中放置到地图的位置不会被另一个线程看到或看得太晚? HashMap也有同样的问题。
答案 0 :(得分:31)
如果你能成功final
那么就这样做。如果您无法成功final
,那么您需要将其设为volatile
。 volatile
适用于字段分配,如果它不是final
,那么有可能(至少每个JMM)一个线程对CHM字段的写入可能不可见另一个线程。重申一下,这是ConcurrentHashMap字段分配而不使用CHM。
话虽如此,你应该真正做到final
。
我们是否需要使地图变得易变,以便读者线程尽快看到一个线程的写入?
如果您所说的写入是使用CHM本身的变异方法(如put
或remove
)完成的,那么您使字段volatile不会产生影响。所有内存可见性保证都在CHM中完成。
是否有可能在一个线程中放置到地图的位置不会被另一个线程看到或看得太晚? HashMap也有同样的问题。
不适用于ConcurrentHashMap。如果您同时使用普通的HashMap,请不要这样做。请参阅:http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html
答案 1 :(得分:10)
volatile
适用于对相应变量的读写操作之前的语义。
可以声明一个字段
volatile
,在这种情况下是Java内存模型 确保所有线程都看到变量的一致值 (§17.4)。
它与变量值引用的对象无关。因此,除非您在多个线程中修改变量,否则您不需要将其变为volatile
。
正如javadoc所述,所有ConcurrentHashMap
方法都充当了内存障碍,
检索反映了最近完成的更新的结果 开始时的行动。 (更正式的是,更新 对于给定密钥的操作与之前的关系具有发生的关系 (非空)检索该密钥报告更新后的值。
答案 2 :(得分:4)
这里有两个子问题:参考对地图的可见性以及值的可见性写入地图。
我们是否需要制作地图......
所以,在你的情况下你的"地图"参考文献未正确发布。这可能会在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的情况下(使用正确锁定的字段&#34; map&#34;)而不是ConcurrentHashMap时,可以使用普通的HashMap。但是,如果您仍然希望使用ConcurrentHashMap以获得更好的性能,那么发布您的&#34; map&#34;的最佳方式正如你所看到的那样,正确的是让这个领域成为最终的。
这是一篇关于安全发布Oracle文章的好文章,顺便说一下: http://shipilev.net/blog/2014/safe-public-construction/
是否可能无法看到或看到一个线程中的地图放置 很晚才有一个不同的主题?
不,如果您没有获得NPE(请参阅第1页)或已正确发布地图,则读者始终会看到编写者所做的所有更改,因为一对ConcurrentHashMap.put / get会产生适当的内存障碍/发生 - 前缘。
HashMap的相同问题
HashMap根本不是线程安全的。方法HashMap.put / get以非线程安全的方式处理映射的内部状态(非原子,没有保证更改状态的线程间可见性),因此,您可能只是破坏了映射的状态。这意味着您必须使用适当的锁定机制(同步部分,ReadWriteLock等)来使用HashMap。并且,作为锁定的结果,您可以实现所需 - 读者始终可以看到编写器所产生的所有更改,因为这些锁会产生内存障碍/ Happens-Before edge。
答案 3 :(得分:4)
不,你不是。
volatile
意味着变量不能缓存在寄存器中,因此将永远是&#34;直写&#34;记忆。这意味着一个线程对变量的更改将对其他线程可见。
在这种情况下,变量是Map的引用。您始终使用相同的地图,因此您不能更改参考 - 而是更改该地图的内容。 (也就是说,Map是 mutable 。)这也意味着您可以,因此应该引用Map final
。
ConcurrentHashMap与HashMap的不同之处在于,您通常可以安全地从中读取和同时从不同线程向写入,而无需外部锁定。但是,如果您希望能够信任任何给定点的大小,执行检查然后写操作等,则需要自己设计。