安全访问多线程HashMap Java

时间:2014-05-25 09:07:45

标签: java multithreading

我有一个具有ConcurrentHashMap的应用程序,它本地存储存储在外部服务器上的数据副本。使用新的数据副本每隔几秒更新一次地图。

我有一个循环,每隔几秒运行一次,可以访问HashMap,并按值的顺序将元素添加到数组中(实际上它实际上要多得多,但这是无关紧要的)。我的问题是,如果数据在创建阵列的过程中发生变化,您可能会在不同的地方重复使用密钥,或者完全遗漏一些。

示例:

// Map starts out like this
1 -> value1
2 -> value2
3 -> value3
4 -> value4

// First 2 elements of array created
value1,value2

// Map is updated
3 -> value3
1 -> value1
2 -> value2
4 -> value4

// Last 2 elements of array created
value1,value2,value1,value4

正如您所看到的,如果地图尚未更改,则数组将为:“value1,value2,value3,value4”或“value2,value3,value1,value4”(如果已在更新后运行)。

这是一些示例代码,它不是原始代码,但它应该解释我的问题:

Map<Integer, String> mapThatGetsUpdated = new ConcurrentHashMap<Integer, String>();
String[] array = new String[mapThatGetsUpdated.size()];

for (int i = 0; i < mapThatGetsUpdated.size(); i++) {
    array[i] = mapThatGetsUpdated.get(i);
}

显而易见的方法是复制地图,然后在使用后将其丢弃,但我希望采用另一种方式,因为地图可能非常大,而且必须每秒复制几次。

1 个答案:

答案 0 :(得分:3)

这是一个问题,因为尽管ConcurrentHashMap是线程安全的,但是您正在执行复合操作而不会锁定。

如果您想保证一致的状态,您必须在阅读时防止写作。

如果您希望读者看到整个读取操作的相同信息,那么您必须;

  • 要么在阅读期间阻止写作
  • 创建开头正在阅读的实体的快照

选项1 (不要这样做)

在阅读和写作过程中用Map锁定synchronized,允许一次发生一次。

这是许多菜鸟程序员的第一个追索权。它将有效地消除ConcurrentHashMap

带来的任何好处

选项2 (不要这样做)

在阅读之前复制Map

注意迭代可能正在执行其他只是循环的操作,您尝试通过仅复制synchronized块中的映射并使用副本来最小化读取锁定。

因为你经常阅读,所以这很可能也无济于事。

选项3

使用AtomicReference。将阅读器中的引用复制到本地引用。作者用全新的Map替换了引用。

此选项的可行性取决于写入的内容。它被称为Copy on Write,如果写入很少且读取很多,则可以获得良好的吞吐量。

它不需要显式锁定,但确实需要编写者做更多的工作。

选项4

使用ReentrantReadWriteLock。将Map更改为HashMap并自行控制并发(编写包装类)。

使用ReadLock锁定读取,使用WriteLock锁定写入。你可以有很多读者,但一次只能有一位作家。

这将保证一致的读取。