java中synchronized与concurrenthashmap的演进本质是锁粒度从粗到细、静态到动态的优化:hashtable用全局synchronized锁,1.7引入segment分段锁,1.8改用cas+桶头synchronized实现更细粒度并发控制。
java中synchronized与concurrenthashmap在段加锁上的演进,本质是“锁的粒度从粗到细、从静态到动态”的持续优化过程。早期用synchronized直接包裹整个map(如hashtable),后来concurrenthashmap 1.7引入segment分段锁,把synchronized(或reentrantlock)下放到段级;到了1.8,则彻底放弃段结构,改用cas配合synchronized锁定单个桶头节点——锁不再绑定固定数量的逻辑分片,而是随数据分布和并发压力实时聚焦。
Hashtable与synchronized的全局锁时代
在ConcurrentHashMap出现前,Hashtable是线程安全Map的主流选择。它的所有public方法(put、get、remove等)都用synchronized修饰,锁的是整个Map实例:
- 任意线程执行任一操作,都会阻塞其他所有线程,无论读写是否冲突
- 读多写少场景下,大量读请求被无谓串行化,吞吐量严重受限
- 扩容时需全表rehash并持有全局锁,时间长、竞争烈、扩展性差
ConcurrentHashMap 1.7:Segment分段锁,synchronized下沉到段级
JDK 1.7将synchronized的使用方式升级为“分而治之”:用16个(默认)Segment替代单一锁,每个Segment继承ReentrantLock,内部维护独立的HashEntry数组:
- 线程根据key两次哈希,先定位Segment,再在其内部执行操作
- 只有操作同一Segment时才发生锁竞争;不同Segment间完全并行
- 虽然底层仍依赖ReentrantLock(本质是synchronized的增强版),但锁作用域从“整个Map”缩小为“一个段”,并发度理论提升至16倍
ConcurrentHashMap 1.8:抛弃Segment,synchronized退守桶头,CAS承担无锁路径
JDK 1.8重构核心结构,取消Segment层级,回归类似HashMap的Node数组+链表/红黑树模型。synchronized不再用于段管理,而是精准作用于单个桶的头节点:
- 插入空桶时,优先用CAS设置新节点——无锁、零阻塞
- CAS失败(说明桶非空或有竞争),才对桶头节点加synchronized锁,在临界区内完成链表追加或树化插入
- 锁粒度从“一段多个桶”细化到“一个桶一个锁”,并发上限由段数(16)跃升至数组长度(初始16,可动态扩容至64、128…)
- 扩容也支持多线程协作迁移,无需全局锁或段级锁阻塞
演进背后的关键取舍
这段历史不是简单替换,而是围绕三个现实约束的反复权衡:
- 内存 vs 性能:Segment带来额外元数据开销(每个Segment含count、modCount、table等字段),小数据量时浪费明显
- 静态 vs 动态:段数初始化后不可变,易出现热点Segment(如key哈希不均);而桶级锁随数组扩容自然分散压力
- 功能 vs 复杂度:1.7的Segment机制支持更复杂的段级统计(如size()尝试多次重试+加锁求和),但1.8用baseCount+CAS+辅助计数器简化实现,同时提升准确性
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











