【问题标题】:concurrent hashmap and copyonwritearraylist并发 hashmap 和 copyonwritearraylist
【发布时间】:2013-09-04 05:16:25
【问题描述】:

我正在尝试使用 ConcurrentHashMap 填充保存键/值的缓存。

我假设使用 CopyOnWriteArrayList 来处理并发性,我将其作为我的键值,但我在下面的代码中遗漏了一些内容,并且在执行多个线程时它会覆盖其值。

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。

【问题讨论】:

  • 阅读Javadoc 的第一句话 - 这不是一个神奇的替代品,而是有一个陷阱。更重要的是,你为什么不直接写信给你的ConcurrentHashMap?您编写代码的方式实际上消除了使用 putIfAbsent 的优势,这与您的 if 完全相同,但以线程安全的方式。
  • 我需要检查该 id 是否已经存在数组列表。如果它存在,我必须为其添加我的价值。
  • 那么你需要一个多图吗?好的,那我试着写一个答案。

标签: java concurrency concurrenthashmap copyonwritearraylist


【解决方案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");

这样,您只需在地图中没有新列表的情况下将其添加到地图中的任何列表中。

【讨论】:

  • 嗨 assyilas,最后应该有一个 map.replace(id,copyArr)
  • @user2596957 为什么? copyArr 是指向地图中列表的引用 - 因此,您对 copyArr 应用的任何操作都将在地图中的列表上执行。
  • 我采用了上面的代码并实现了我的开发版本如下。
  • CopyOnWriteArrayListsubscriberArr = CacheUtils .getSubscriberMap().get(syncDet.getCardNumber()); if (subscriberArr == null) {subscriberArr = new CopyOnWriteArrayList(); CopyOnWriteArrayList refArr = CacheUtils .getSubscriberMap().putIfAbsent(syncDet.getCardNumber(),subscriberArr); if (refArr != null) {subscriberArr = refArr; } } 订阅者Arr.add(syncDet.getSubScriber());
  • 否。更新了上面相同的代码。您发现该代码有什么问题吗?
【解决方案2】:

您的实现存在一些问题。

首先,您正在以非线程安全的方式检查给定键是否没有列表。完全有可能两个线程可以执行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 解释了如何)。

【讨论】:

  • 尽管您的 cmets 有意义,但无需额外同步即可使其正常工作。这只是使用 putIfAbsent 返回的值的问题。
  • @assylias :如果 OP 需要绝对一致性,并且放弃使用 CopyOnWriteArrayList。但我猜你的意思是第一点,其中“同步”这个词可能是模棱两可的。我会修改措辞以避免混淆,谢谢。
  • 感谢 TheTerribleSwiftTomato 强调了不使用 putIfAbsent 的返回对象的错误。问题已解决。
  • @user2596957 :不客气。请accept one of the answers(以最能帮助您的为准)以便将此问题标记为已解决。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-03
相关资源
最近更新 更多