use*_*844 36 java concurrency java.util.concurrent
我们有一个共享的ConcurrentHashMap,由2个线程读取和写入.
class Test {
ConcurrentHashMap map;
read() {
map.get(object);
}
write() {
map.put(key, object);
}
}
Run Code Online (Sandbox Code Playgroud)
我们是否需要使地图变得易变,以便读者线程尽快看到一个线程的写入?
是否有可能在一个线程中放置到地图的位置不会被另一个线程看到或看得很晚?HashMap也有同样的问题.
Joh*_*int 32
如果你能做到,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
这里有2个子问题:参考visability到地图和值visability写入地图。
我们需要做地图吗...
因此,在您的情况下,“地图”参考没有正确发布。这可能会在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);
}
}
Run Code Online (Sandbox Code Playgroud)
当然,在第1页的情况下(使用正确锁定的字段“地图”时),可以使用普通的HashMap代替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 是可变的。)这也意味着您可以并且因此应该引用 Map final
。
ConcurrentHashMap 与 HashMap 的不同之处在于,您通常可以从不同的线程同时安全地读取和写入它,而无需外部锁定。但是,如果您希望能够信任任何给定点的大小,执行先检查后写入操作等,则需要自己设计。