我有一个具有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);
}
显而易见的方法是复制地图,然后在使用后将其丢弃,但我希望采用另一种方式,因为地图可能非常大,而且必须每秒复制几次。
答案 0 :(得分:3)
这是一个问题,因为尽管ConcurrentHashMap
是线程安全的,但是您正在执行复合操作而不会锁定。
如果您想保证一致的状态,您必须在阅读时防止写作。
如果您希望读者看到整个读取操作的相同信息,那么您必须;
选项1 (不要这样做)
在阅读和写作过程中用Map
锁定synchronized
,允许一次发生一次。
这是许多菜鸟程序员的第一个追索权。它将有效地消除ConcurrentHashMap
。
选项2 (不要这样做)
在阅读之前复制Map
。
注意迭代可能正在执行其他只是循环的操作,您尝试通过仅复制synchronized
块中的映射并使用副本来最小化读取锁定。
因为你经常阅读,所以这很可能也无济于事。
选项3
使用AtomicReference
。将阅读器中的引用复制到本地引用。作者用全新的Map
替换了引用。
此选项的可行性取决于写入的内容。它被称为Copy on Write,如果写入很少且读取很多,则可以获得良好的吞吐量。
它不需要显式锁定,但确实需要编写者做更多的工作。
选项4
使用ReentrantReadWriteLock
。将Map
更改为HashMap
并自行控制并发(编写包装类)。
使用ReadLock
锁定读取,使用WriteLock
锁定写入。你可以有很多读者,但一次只能有一位作家。
这将保证一致的读取。