如何在无synchronized或lock的情况下向ConcurrentHashMap<String, List<String>>添加新元素?
解决ConcurrentHashMap中添加元素时的丢失问题
嘿,这个场景我太熟悉了!你遇到的问题核心是:当你拿到List之后,其他线程可能已经把对应的key从ConcurrentHashMap中移除了,这时候你往手里的List加元素,自然不会被反映到Map里,相当于白加了。而且你不想用synchronized或者显式锁,那咱们就用ConcurrentHashMap本身提供的原子方法来解决。
方案一:使用compute方法(最稳妥)
ConcurrentHashMap的compute方法是针对单个key的原子操作,它会帮你处理“获取-修改-更新”的整个流程,而且在执行期间,该key的其他操作会被安全地同步。直接把添加元素的逻辑放到compute的lambda里:
private ConcurrentHashMap<String, List<String>> entries = new ConcurrentHashMap<>(); public void record(String key, String value) { entries.compute(key, (k, existingList) -> { // 如果列表不存在,就创建一个同步列表 List<String> list = existingList; if (list == null) { list = Collections.synchronizedList(new ArrayList<>()); } // 直接在当前Map关联的列表里添加元素 list.add(value); // 返回更新后的列表(或者新创建的) return list; }); }
为什么这个能解决问题?因为compute方法会确保:
- 你拿到的
existingList是当前Map中该key的最新值 - 在lambda执行的整个过程中,其他线程对该key的
put、remove、compute等操作都会被同步,不会出现“拿到List后key被移除”的情况 - 最后返回的列表会被重新关联到该key上,哪怕中间有其他线程尝试修改,也会被
compute的原子性覆盖(当然这取决于业务场景,这里咱们要的是确保元素被添加)
方案二:用computeIfAbsent + 二次检查(更轻量,但要注意边界)
如果你觉得compute有点重,也可以用computeIfAbsent先确保列表存在,然后添加元素,但这时候需要额外检查一下:添加元素前,Map里的key是否还关联着你拿到的列表。不过这个方法有个小前提:你的业务场景中,key被移除的频率不高,或者你能接受极小概率的元素丢失(虽然概率很低)。
private ConcurrentHashMap<String, List<String>> entries = new ConcurrentHashMap<>(); public void record(String key, String value) { List<String> values = entries.computeIfAbsent(key, k -> Collections.synchronizedList(new ArrayList<>())); // 添加元素前,检查当前Map中的列表是不是我们拿到的这个 if (entries.get(key) == values) { values.add(value); } // 如果不相等,说明key已经被移除或者替换了,这时候放弃添加(或者可以重试一次) }
这个方法的思路是:用computeIfAbsent原子性地获取或创建列表,然后再确认一次这个列表还在Map里。如果这时候列表已经被替换或移除,那就不添加了——当然你也可以选择重试整个流程,确保元素被添加进去。
为什么原来的代码会出问题?
你原来的代码里,get和add是两个独立的操作,中间有时间窗口:
- 你
get到了列表(或者通过putIfAbsent创建了一个) - 这时候其他线程
remove了这个key,Map里已经没有这个关联了 - 你往手里的列表
add元素,但这个列表已经和Map没关系了,所以元素丢失
而用compute方法的话,整个“获取列表-添加元素-更新Map”的过程是原子的,没有时间窗口给其他线程移除key。
内容的提问来源于stack exchange,提问作者Hel
相关产品推荐
相关产品推荐

