如何在CopyOnWriteArrayList中安全添加不重复元素(保留插入顺序)?
线程安全的有序无重复列表实现优化方案
问题描述
我需要保证代码线程安全,因此使用了
CopyOnWriteArrayList:List<String> fruits = new CopyOnWriteArrayList<>();我希望列表中不存在重复元素,同时还要保留插入顺序,因此无法使用Set。我编写了如下代码:
public boolean add(String fruit) { synchronized (fruits) { if (fruits.contains(fruit)) { return false; } fruits.add(fruit); } return true; }由于我知道
CopyOnWriteArrayList本身已是并发安全的,因此将其add方法放在synchronized块中似乎并非良策,想请教是否有更优的解决方案?
优化方案
方案一:结合ConcurrentHashMap做原子校验 + CopyOnWriteArrayList维护顺序
利用ConcurrentHashMap的原子性操作做重复判断,避免全局锁,同时保留CopyOnWriteArrayList的有序性:
private final ConcurrentHashMap<String, Boolean> existChecker = new ConcurrentHashMap<>(); private final CopyOnWriteArrayList<String> fruits = new CopyOnWriteArrayList<>(); public boolean add(String fruit) { // putIfAbsent是原子操作,返回null表示元素之前不存在 Boolean existed = existChecker.putIfAbsent(fruit, Boolean.TRUE); if (existed == null) { fruits.add(fruit); return true; } return false; }
核心优势是用ConcurrentHashMap的原子操作替代同步块,避免全局锁竞争。唯一需要注意:极端情况下如果putIfAbsent成功后、fruits.add前发生异常,会出现map有记录但列表无元素的情况,可根据业务场景做补偿处理。
方案二:LinkedHashSet加显式锁(适合读多写少场景)
LinkedHashSet天然支持有序+无重复,套一层显式锁即可实现线程安全,比CopyOnWriteArrayList的写操作性能更优(避免数组复制开销):
private final LinkedHashSet<String> fruits = new LinkedHashSet<>(); private final ReentrantLock lock = new ReentrantLock(); public boolean add(String fruit) { lock.lock(); try { // LinkedHashSet的add方法本身会判断重复,返回是否新增成功 return fruits.add(fruit); } finally { lock.unlock(); } } // 读操作如需线程安全,同样要加锁或返回快照 public List<String> getFruits() { lock.lock(); try { return new ArrayList<>(fruits); } finally { lock.unlock(); } }
方案三:自定义锁保护的ArrayList(适合写多读少场景)
如果对内存开销敏感,可基于ArrayList+ReentrantLock实现,所有读写操作都在锁保护下进行:
private final List<String> fruits = new ArrayList<>(); private final ReentrantLock lock = new ReentrantLock(); public boolean add(String fruit) { lock.lock(); try { if (fruits.contains(fruit)) { return false; } return fruits.add(fruit); } finally { lock.unlock(); } } public List<String> getFruits() { lock.lock(); try { return new ArrayList<>(fruits); } finally { lock.unlock(); } }
这种方案完全自定义逻辑,避免CopyOnWriteArrayList的数组复制开销,但读操作也需要加锁,适合写操作频率较高的场景。
内容的提问来源于stack exchange,提问作者Ahmed Bilal
相关产品推荐
相关产品推荐

