0

我正在使用以下内容HashMap

HashMap<String,String> hm = new HashMap<String,String>();

在不同线程中运行的三个不同Runnable的 s 将从 中读取hm,而第四个线程Runnable将从映射中添加和删除键/值条目。根据Java 文档,只要多个线程可能同时与之交互,我就应该同步对这个映射的访问。但是,出于性能原因,我不希望三个读取器线程相互阻塞,因为它们只是从地图中读取。我只希望在第四个线程添加/删除映射条目时发生阻塞。据我了解,调用Collections.synchronizedMap(...)将同步对地图的任何类型的访问。我怎样才能做到这一点?

4

3 回答 3

6

每当多个线程可能同时与它交互时,应该同步对这个映射的访问。但是,出于性能原因,我不希望三个读取器线程相互阻塞,因为它们只是从地图中读取。

尽管读取器不需要阻塞,但您确实需要在读取和写入时同步内存,否则读取可能会获得部分更新的映射,这可能会产生异常。请参阅内存同步教程

多线程编程的难点在于每个线程都有自己的本地 CPU 内存缓存,然后与中央内存同步。一个正在读取的线程不会看到同步写入所做的更新。更糟糕的是,他们可能会看到地图内部内存的部分更新,这会导致异常。

要同步您的地图,您应该将其包装在一个Collections.synchronizedMap(...)调用中,或者(正如@SLaks 在评论中提到的那样),使用ConcurrentHashMap为多个读者/作者构建的。@SLaks 还提到了,ReaderWriterLock但是 CHM 会更高效,需要维护的代码也更少。

更具体地说,在“阻塞”方面,CHM 对 map 进行分区,并在处理多个读取器和写入器时以最小的阻塞做得很好。get例如,从 CHM 获取实际执行锁定的唯一时间是查找在调用之前插入到映射中的对象时。

除非分析器告诉您这是性能问题,否则我不会担心 CHM 的性能。

于 2013-10-21T22:18:44.823 回答
1

根据您的要求(以及与读取相比的写入频率),CopyOnWriteMap可能是一种替代方案。

于 2013-10-21T22:36:41.397 回答
0

所需的同步级别实际上取决于您的特定用例。@SLaks 使用ConcurrentHashMap的建议将起作用,只要您不需要从 Map 读取一次即可对相同数据进行下一次操作。例如:

if (instance.containsKey("foo")) {
    System.out.println(instance.get("foo"));
}

可能会失败,因为 ConcurrentHashMap 可以在两个函数调用之间更改。如果您需要此类保证,则应使用ReadWriteLock,因为您可以在读/写期间保持 Map 锁定。例如,上面的相同代码如下所示:

rwlock.readLock().lock();
try {
    if (instance.containsKey("foo")) {
        System.out.println(instance.get("foo"));
    }
} finally {
    rwlock.readLock().unlock();
}

(在这种情况下,实例可以是 Map<> 的任何实现,它不必是 ConcurrentHashMap)。

于 2013-10-21T22:38:13.767 回答