跳转到主内容
极星编程网:以代码为星,赴技术山海!

在Java中并发容器解决了什么问题_Java线程安全集合解析

普通集合线程不安全因其未同步,多线程读写会引发ConcurrentModificationException、数据丢失或结构损坏;ConcurrentHashMap通过CAS加锁单桶、无锁读、协作扩容等机制避免锁竞争。 为什么普通集合在多线程下会出错 因为
ArrayList
、
HashMap
、
HashSet
这些类没做同步,多个线程同时读写时,可能触发
ConcurrentModificationException
,或产生 数据丢失 、脏读、结构损坏(比如链表成环)。 典型现象:遍历
ArrayList
时另一个线程调用
add()
,抛出
ConcurrentModificationException
;或者
HashMap
在扩容时多个线程同时操作,导致死循环(JDK 7)或数据覆盖(JDK 8+)。 不是“偶尔出错”,而是**未定义行为**——结果不可预测,且难以复现
Vector
和
Hashtable
虽线程安全,但用的是全表锁,吞吐量极低,不适合高并发场景 手动加
synchronized
锁粒度难控制:锁太粗影响性能,锁太细容易漏掉临界区
ConcurrentHashMap
是怎么避免锁竞争的 JDK 8 的
ConcurrentHashMap
放弃了分段锁(
Segment
),改用「CAS + synchronized 锁单个桶(bin)」策略,写操作只锁定哈希表中实际发生修改的那个链表头或红黑树根节点。 读操作完全无锁,依赖
volatile
变量和
Unsafe
的有序读取保证可见性 扩容时支持多线程协作迁移,老表仍可读,新旧表并存过渡,不阻塞读写
size()
不再是 O(1),而是对每个 bin 的计数求和,可能有短暂误差(最终一致) 不支持
containsKey()
和
get()
之外的复合操作原子性,比如“检查不存在再 put”得用
computeIfAbsent()
哪些并发容器对应哪些使用场景 选错容器会导致功能缺失或性能浪费。关键看是否需要强一致性、是否允许重复、是否要阻塞等待: Eclipse导入Android或其他的JAVA项目的正确方法 WORD版 本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看 下载 立即学习 “ Java免费学习笔记(深入) ”; 需要线程安全的「栈」→ 用
ConcurrentLinkedDeque
(非阻塞)或
ArrayBlockingQueue
(阻塞,但得自己模拟栈逻辑);别用
Stack
或同步包装的
LinkedList
生产者-消费者模型 → 优先选
ArrayBlockingQueue
(有界、可重入锁)或
LinkedBlockingQueue
(无界、双锁分离);高吞吐低延迟场景考虑
Disruptor
(非 JDK) 需要线程安全的「计数器」→ 直接用
AtomicInteger
,别用
ConcurrentHashMap
存一堆计数器,除非 key 动态可变 读远多于写的配置缓存 →
CopyOnWriteArrayList
合适;但千万别在频繁修改的列表上用它,每次写都复制整个数组 常见误用和隐蔽陷阱 并发容器不是银弹,很多问题出在「以为线程安全,其实只是容器本身安全」:
ConcurrentHashMap
的
keySet()
返回的集合不是实时快照,遍历时若其他线程修改,不会抛异常,但可能漏数据或重复 —— 需要强一致性遍历请用
new ArrayList(map.keySet())
先拷贝
ConcurrentLinkedQueue
的
size()
是 O(n),高并发下返回值可能瞬间过期,不能用于条件判断(如
if (q.size() > 0) q.poll()
)
CopyOnWriteArrayList
的迭代器不支持
remove()
,调用会抛
UnsupportedOperationException
;它的
set()
也是写时复制,不是原地更新 所有并发容器都不保证「复合操作」原子性,比如
map.containsKey(k) ? map.get(k) : null
依然存在竞态,必须换成
map.getOrDefault(k, null)
或
computeIfPresent()
最常被忽略的一点:并发容器解决的是「容器自身结构与元素引用的线程安全」,不解决元素对象内部状态的线程安全。如果往
ConcurrentHashMap
里放可变对象,还得确保
MyMutableObj
自身是线程安全的,或访问时额外同步。

相关文章