我正在尝试使用ConcurrentHashMap填充一个包含键/值的缓存。
我假设使用CopyOnWriteArrayList
负责并发,我将其作为我的密钥的值,但是我在下面的代码中遗漏了一些东西,当多个线程执行时它会覆盖它的值。 / p>
if (testMap.get(id) == null) {
CopyOnWriteArrayList<String> copyArr = new CopyOnWriteArrayList<String>();
copyArr.add("Add Value");
testMap().putIfAbsent(id, copyArr);
} else {
testMap.put(id,testMap.get().add("Append Value"));
}
如何保护从多个线程创建CopyOnWriteArrayList
的代码。
以下是根据以下建议修改后的代码版本。
CopyOnWriteArrayList<Subscriber> subscriberArr = CacheUtils.getSubscriberMap().get(syncDet.getCardNumber());
if (subscriberArr == null) {
subscriberArr = new CopyOnWriteArrayList<Subscriber>();
CopyOnWriteArrayList<Subscriber> refArr =
cacheUtils.getSubscriberMap().putIfAbsent(syncDet.getCardNumber(), subscriberArr);
if (refArr != null) {
subscriberArr = refArr;
}
}
subscriberArr.add(syncDet.getSubScriber());
在迭代订户地图时,我没有看到价值对象。大小为0。
答案 0 :(得分:1)
您的实施存在一些问题。
首先,您正在以非线程安全的方式检查给定密钥是否没有列表。在任何一个线程放置密钥之前,两个线程可以完全执行if (testMap.get(id) == null)
。这不会导致密钥本身被覆盖。
但是,会生成两个列表,并且因为您在设置密钥之前在不安全的if
块中向这些列表添加元素 ,在实际的键 - >值映射中出现的元素是任何人的猜测。
此外,绝对没有必要:
testMap.put(id,testMap.get(id).add("Append Value"));
在这种情况下,列表实例已经在地图中,您只需get
并添加值即可。请注意,这也可能会破坏您以前的键分配!
第二个,潜在的问题是您使用的是CopyOnWriteList
,它会在添加新元素时创建新的支持数组。这里有两个后果:
add
操作已同步(通过ReentrantLock
),但get
不是,您可能会获得不同的列表内容短时间内不同的线程(列表最终对于添加一致)。这实际上是按设计 - CopyOnWriteArrayList
适用于高读/低写操作。你至少有两种方法:
put
次操作,即
putIfAbsent
。get
获取的值。CopyOnWriteArrayList
。使用带有“手动”同步的普通列表。你可以用例如Guava的Multimap,例如as this one,带有同步包装器以避免麻烦(Javadoc解释如何)。答案 1 :(得分:1)
您需要首先检索相应的列表然后填充它。类似的东西:
List<String> copyArr = testMap.get(id);
if (copyArr == null) {
copyArr = new CopyOnWriteArrayList<String>();
List<String> inMap = testMap.putIfAbsent(id, copyArr);
if (inMap != null) copyArr = inMap; // already in map
}
copyArr.add("Add Value");
这样你只会在地图中放入一个新列表,如果还没有,你可以将你的项目添加到地图中的任何列表中。