ConcurrentHashMap 不可用 HashMap 替代,因其在多线程下 put/get/扩容会引发死循环(JDK7)或数据丢失(JDK8+),而 ConcurrentHashMap 通过分段锁(JDK7)或 CAS+synchronized(JDK8)保证线程安全,读操作几乎无锁。

ConcurrentHashMap 为什么不能直接用 HashMap 替代
因为 HashMap 在多线程环境下 put、get 或扩容时可能引发死循环(JDK 7)或数据丢失(JDK 8+),而 ConcurrentHashMap 通过分段锁(JDK 7)或 CAS + synchronized(JDK 8)保证线程安全,且读操作几乎无锁。
常见错误现象:ConcurrentModificationException 在遍历时仍可能出现——不是因为并发修改检测失效,而是你用了 iterator() 后在另一个线程调用 remove() 或 clear(),而迭代器不感知这些变更。
- 遍历推荐用
forEach()、keySet().forEach()或entrySet().parallelStream(),避免显式Iterator - 不要在遍历中调用
put()/remove()—— 即使是ConcurrentHashMap,也不支持“边遍历边结构性修改” - JDK 8+ 中
computeIfAbsent()是原子的,比先get()再put()更安全
CopyOnWriteArrayList 适合什么场景,又为什么慢
CopyOnWriteArrayList 每次写操作(add、set、remove)都会复制整个底层数组,读操作完全无锁。它只适合「读多写极少」且写操作不敏感延迟的场景,比如监听器列表、配置白名单缓存。
性能影响明显:10 万元素 add 一次可能触发数 MB 数组复制;频繁写会导致 GC 压力陡增。
立即学习“Java免费学习笔记(深入)”;
- 别把它当普通
ArrayList用在循环内反复 add —— 这是典型误用 - 它的
iterator()返回的是快照,后续写操作对当前迭代器不可见,这点和ConcurrentHashMap的弱一致性不同,是强快照语义 - 没有
contains()的高效实现,内部是线性扫描,大数据量下慎用
BlockingQueue 实现线程间解耦的关键参数怎么选
以 ArrayBlockingQueue 和 LinkedBlockingQueue 最常用,但容量策略差异极大:
-
ArrayBlockingQueue是有界队列,构造时必须指定容量,内部用单个ReentrantLock控制入队/出队,吞吐略低但内存可控 -
LinkedBlockingQueue默认构造是无界(Integer.MAX_VALUE),看似安全,实则容易掩盖生产者过快问题,导致 OOM - 若用
new LinkedBlockingQueue(1024)显式设界,它会为入队和出队分别使用独立锁(takeLock/putLock),吞吐更高
错误现象:用无界队列做日志缓冲,突发流量把堆打满;或用 SynchronousQueue(实际无缓冲)却没配够消费者线程,导致生产者阻塞超时。
为什么 Collections.synchronizedList 不如 CopyOnWriteArrayList 或 BlockingQueue
Collections.synchronizedList(new ArrayList()) 只是对每个方法加了 synchronized(this),锁粒度太粗:一次遍历要持锁全程,所有线程串行排队,本质是“伪并发”。而 CopyOnWriteArrayList 读不加锁,BlockingQueue 分离生产和消费路径。
更隐蔽的问题是:即使加了 synchronized,遍历仍需手动同步 —— 否则仍可能抛 ConcurrentModificationException 或读到不一致状态。
- 正确遍历方式:
synchronized (list) { list.iterator().forEachRemaining(...); } - 它不提供任何等待/通知机制,无法实现“生产者等空位、消费者等数据”的协作逻辑
- 在高竞争场景下,锁争用远高于
ConcurrentHashMap或StampedLock等现代同步工具
真正需要线程安全集合时,优先按场景选专用类,而不是套一层 synchronized 包装器——后者只是最省事的错觉。











