项目里有共享集合被多个线程读写,你会怎么判断该加锁还是换成并发集合?
这个问题我会先说项目结论:同步包装器通常用一把互斥锁保护普通集合的单个方法,结构简单但竞争较大;并发集合针对访问模式设计,使用分段、CAS、写时复制或无锁算法提升并发度,并提供 putIfAbsent、compute 等原子操作。
我会先交代项目背景和选型结论。比较同步包装器与并发容器的锁粒度、迭代语义和复合操作。同步包装器通常用一把互斥锁保护普通集合的单个方法,结构简单但竞争较大;并发集合针对访问模式设计,使用分段、CAS、写时复制或无锁算法提升并发度,并提供 putIfAbsent、compute 等原子操作。 常见选择包括 ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue 和各种 BlockingQueue,它们的一致性与阻塞语义并不相同。
具体落地时,我会沿着实际调用链来讲。沿着「识别并发访问模式 → 选择映射队列或快照 → 执行单操作原子更新 → 建立复合操作边界 → 观测竞争与容量」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 识别并发访问模式 先区分读多写少、生产消费、计数聚合还是有序调度,不存在适合所有模式的“并发集合”。 java.util.concurrent 包:按 Map/Queue/Deque/SkipList 选择。 BlockingQueue#put/take:容量、条件等待和中断。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。
参数和容量不能靠默认值,我会结合业务量来定。按真实读写比压测 ConcurrentHashMap、同步包装与快照;队列测试必须让消费者故意变慢,验证背压和拒绝。
效果要用数据证明,线上问题也要能闭环。固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「队列容量」为主基线,记录值应满足「按峰值恢复窗口计算」;同时保存 队列长度与等待时间、热点键更新冲突,使后续变化能够回到同一时间轴比较。 代码用 containsKey 后再 put 标记任务,两个线程都通过检查。ConcurrentHashMap 没有损坏,但两步之间存在竞态。改用 putIfAbsent 返回值决定唯一执行者,并在失败回滚时明确标记生命周期后才解决。 用线程安全集合拼出非原子业务流程:队列长度与等待时间:把检查再执行折叠为原子 API。
最后我会主动说明这个方案不适合什么场景。方案:更适合的场景:主要收益:代价与边界。 ConcurrentHashMap:高并发键值访问:桶级并发与原子复合 API:不支持跨键事务。 BlockingQueue:有界生产消费与背压:容量、等待语义明确:锁竞争和队头阻塞需评估。 ConcurrentSkipListMap:并发有序键与范围查询:有序、导航操作丰富:O(log n) 且节点开销较高。