为何JDK11的ConcurrentHashMap的tabAt/setTabAt无需volatile语义?
关于ConcurrentHashMap中tabAt/setTabAt语义变化的解惑
核心结论:你的理解存在部分偏差,acquire/release语义足以保证正确性
1. 关键前提:tab数组引用本身是volatile的
不管是JDK8还是JDK11,Node<K,V>[] tab这个数组的引用都是volatile修饰的,这是保证可见性的基础。虽然数组元素不是volatile,但结合acquire/release语义的内存屏障,完全可以替代volatile读写的效果。
2. acquire/release语义如何保证happens-before与可见性
- setTabAt的release语义:它始终在锁区域内调用,锁的释放本身就自带release屏障,加上setTabAt的release写入,会确保所有前置的写操作(包括对Node节点的修改)都刷新到主内存,后续任何通过acquire语义读取的线程都能看到这些修改。
- tabAt的acquire语义:即使在get这种无锁场景下调用,acquire屏障会禁止读取操作重排序到屏障之前,同时强制从主内存加载最新的数组元素值,刚好和setTabAt的release语义形成完整的happens-before链,保证读取到的是最新状态。
3. 无锁get方法的正确性保障
ConcurrentHashMap的Node节点本身是不可变的(TreeNode的修改也有锁或CAS做同步),即使get过程中遇到数组扩容,也只会读取到旧数组的快照或新数组的合法元素,不会出现脏读。再配合tabAt的acquire语义,完全能保证读取的可见性。
4. JDK11优化的合理性
JDK8用volatile是保守实现,当时对acquire/release语义的应用还不够广泛;JDK11改用acquire/release是更精准的性能优化——既满足了并发安全的需求,又比完整的volatile读写开销更低,因为volatile会带来更严格的内存屏障,而acquire/release是按需提供的屏障。
内容的提问来源于stack exchange,提问作者Oliver
相关产品推荐
相关产品推荐

