Java 并发hashmap和copyonwritearraylist

Java 并发hashmap和copyonwritearraylist,java,concurrency,concurrenthashmap,copyonwritearraylist,Java,Concurrency,Concurrenthashmap,Copyonwritearraylist,我试图用ConcurrentHashMap填充一个缓存,该缓存保存键/值 我假设使用一个CopyOnWriteArrayList来处理并发性,我把它作为我的键的值,但是我在下面的代码中缺少了一些东西,当多个线程执行时,它会覆盖它的值 if (testMap.get(id) == null) { CopyOnWriteArrayList<String> copyArr = new CopyOnWriteArrayList<String>(); copyArr.a

我试图用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"));
}

在迭代订户映射时,我没有看到值对象。大小为0。

您所做的实现存在几个问题

首先,您要以非线程安全的方式检查给定密钥是否没有列表。两个线程完全可以执行
if(testMap.get(id)=null)
然后再执行
if(testMap.get(id)=null)。这不会导致密钥本身被覆盖

但是,两个列表都会生成,而且由于您在设置键之前将元素添加到不安全的
if
中的那些列表中,所以实际的键->值映射中会出现哪些元素是任何人都可以猜测的

此外,完全没有必要这样做:

testMap.put(id,testMap.get(id).add("Append Value"));
在本例中,列表实例已经在映射中,您只需
获取它并添加值。请注意,这也可能会打乱您以前的密钥分配


第二个潜在问题是,您使用的是
CopyOnWriteList
,它在添加新元素时创建一个新的备份数组。这里有两个后果:

  • 如果有很多额外的东西,那就太贵了
  • 由于
    add
    操作是同步的(通过
    ReentrantLock
    ),但是
    get
    不是同步的,因此您可能会在短时间内在不同的线程中获得不同的列表内容(但是,对于添加,列表最终是一致的)。这实际上是出于设计——
    CopyOnWriteArrayList
    面向高读/低写操作
你至少有两条出路:

  • 以线程安全的方式执行
    put
    操作,即。
    • 仅使用
      putIfAbsent
    • 不要向列表的本地副本添加任何值,只添加从
      get
      获取的值
  • 如果您需要绝对一致性而不是最终一致性,那么根本不要使用
    CopyOnWriteArrayList
    。改为使用带有“手动”同步的普通列表。您可以将Guava的Multimap等与同步包装器一起使用,以避免麻烦(Javadoc解释了如何使用)

您需要首先检索适当的列表,然后填充它。比如:

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");
List copyArr=testMap.get(id);
if(copyArr==null){
copyArr=新的CopyOnWriteArrayList();
List inMap=testMap.putIfAbsent(id,copyArr);
if(inMap!=null)copyArr=inMap;//已在映射中
}
复制增值(“增值”);

这样,只有在地图上没有新列表的情况下,你才能在地图上添加一个新列表,并将你的项目添加到地图上的任何列表中。

阅读-这不是一个神奇的替代品,而是一个陷阱。更重要的是,为什么不直接向
ConcurrentHashMap
写入?您编写代码的方式实际上消除了使用
putIfAbsent
的优势,它与
if
的功能完全相同,但采用线程安全的方式。我需要检查该id的arraylist是否已经存在。如果存在,我必须将我的值添加到其中。那么您需要一个多重映射吗?好吧,那我就试着写一个答案。虽然你的评论很有意义,但没有必要进行额外的同步。这只是使用putIfAbsent返回的值的问题。@assylias:如果OP需要绝对一致性,则会出现这种情况,并且放弃使用
CopyOnWriteArrayList
。但我想你指的是第一点,“synchronize”一词可能有歧义。我将编辑措辞以避免混淆,谢谢。感谢TerriblesWiftTomato突出显示未使用putIfAbsent返回对象的错误。问题解决了。@user2596957:不客气。请(以对您帮助最大的为准)将此问题标记为已解决。嗨,assyilas,是否有地图。在end@user2596957为什么?
copyArr
是一个指向映射中列表的引用-因此,您应用于
copyArr
的任何操作都将在映射中的列表上执行。我采用了上面的代码并实现了我的开发版本,如下所示。CopyOnWriteArrayList subscriberArr=CacheUtils.getSubscriberMap().get(syncDet.getCardNumber()); 如果(subscriberArr==null){subscriberArr=new CopyOnWriteArrayList();CopyOnWriteArrayList重构=CacheUtils.getSubscriberMap().putIfAbsent(syncDet.getCardNumber(),subscriberArr);如果(refArr!=null){subscriberArr=refArr;}}subscriberArr.add(syncDet.getSubScriber());没有。更新了上面相同的代码。你觉得那代码有什么问题吗?
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");