concurrenthashmap 不可用 hashmap 替代,因其在多线程下扩容会导致死循环(jdk7)或数据丢失(jdk8+),而 concurrenthashmap 通过分段锁或 cas+synchronized 保证线程安全,读操作无锁。

ConcurrentHashMap 为什么不能直接用 HashMap 替代
因为 HashMap 在多线程环境下会因扩容导致死循环(JDK 7)或数据丢失(JDK 8+),而 ConcurrentHashMap 通过分段锁(JDK 7)或 CAS + synchronized(JDK 8)保证线程安全,且读操作完全无锁。
常见误用:ConcurrentHashMap 的 size() 返回的是估算值,高并发下可能不准;如需精确计数,应改用 LongAdder 或手动加锁统计。
- 不要用
concurrentMap.put(key, value)后立刻调用concurrentMap.size()做条件判断 -
computeIfAbsent()是线程安全的,适合缓存初始化场景;但注意其内部 lambda 不应有副作用或阻塞操作 - JDK 8+ 中,
ConcurrentHashMap不再继承AbstractMap,所以不能直接传给期望Map但内部调用了entrySet().iterator()并假设线程安全的旧代码
CopyOnWriteArrayList 适合哪些写少读多的场景
CopyOnWriteArrayList 每次写操作都复制整个数组,因此写性能差、内存开销大,但迭代器绝对安全——不会抛 ConcurrentModificationException,也不需要额外同步。
典型适用场景:监听器列表、配置项白名单、状态枚举集合等基本不变更的只读为主结构。
立即学习“Java免费学习笔记(深入)”;
- 避免在 for-each 循环中调用
add()或remove(),虽然不报错,但新元素不会被本轮遍历看到 - 它的
get(int index)是 O(1),但contains(Object o)是 O(n) 且每次都要遍历当前快照,大数据量时慎用 - 不能用于实现“实时一致性”逻辑,比如两个线程分别读取 size() 和 get(0),中间可能已有其他线程修改过,二者看到的不是同一份快照
BlockingQueue 实现生产者-消费者时选哪个实现类
核心看吞吐量、公平性、是否需要容量限制和响应中断:
-
ArrayBlockingQueue:固定大小、基于数组、可选公平策略,适合资源明确受限的场景(如数据库连接池) -
LinkedBlockingQueue:默认无界(Integer.MAX_VALUE),实际使用中建议显式指定容量,否则可能掩盖 OOM 风险;吞吐略高于ArrayBlockingQueue -
SynchronousQueue:不存储元素,每个put()必须等待配对的take(),适合任务交接型线程池(如Executors.newCachedThreadPool()底层) -
PriorityBlockingQueue:无界、支持优先级排序,但不保证公平性,且迭代器不按优先级顺序返回
注意:offer(e, timeout, unit) 和 poll(timeout, unit) 会响应中断;而 put(e) / take() 在阻塞期间被中断会抛 InterruptedException,必须正确处理。
AtomicInteger 等原子类与 synchronized 的性能差异在哪
原子类本质是利用 CPU 提供的 CAS(Compare-And-Swap)指令实现无锁更新,在低竞争下性能显著优于 synchronized;但在高竞争下,CAS 失败重试可能导致大量 CPU 自旋,此时 synchronized 的挂起/唤醒机制反而更省资源。
另外,原子类只能保证单个变量的原子性,无法组合多个操作(比如“先读再写”且整体原子)——这时仍需 synchronized 或 ReentrantLock。
-
AtomicInteger.getAndIncrement()返回旧值,incrementAndGet()返回新值,别混淆语义 -
LongAdder在高并发计数场景比AtomicLong更优,它通过分散热点(cell 分片)降低 CAS 冲突,但最终值需调用sum()获取,不是实时精确值 - 所有原子类的字段必须声明为
volatile才能保证可见性,但 JDK 已封装好,用户无需手动加 volatile
真正复杂的并发逻辑,比如跨多个集合的状态协同、条件等待、可中断等待,还是得回到 java.util.concurrent.locks 包里的工具,原子类和并发集合只是基石,不是银弹。










