卡码笔记
首页
计算机基础
C++
Java
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
首页
计算机基础
C++
Java
面经
笔记广场 (opens new window)
代码随想录 (opens new window)
  • 基础与面向对象

  • 集合

    • Java常见集合类有哪些?
    • Map接口有哪些实现类?
    • ArrayList和LinkedList的区别?
    • ArrayList和普通数组的区别是什么?
    • ArrayList扩容机制是什么?
    • HashMap、HashSet、HashTable和ConcurrentHashMap的区别?
    • Java中HashMap的底层实现是什么?
    • HashMap的put方法的流程是怎样的?
    • HashMap的扩容机制?
    • HashMap为什么是线程不安全的?如何实现线程安全?
    • Hash冲突的解决方案有哪些?HashMap是怎样解决Hash冲突的?
    • concurrentHashMap如何保证线程安全?
      • 简要回答
      • 详细回答
      • 适用场景
      • 代码示例
      • 知识图解
      • 知识扩展
    • 哪些集合类是线程安全的,哪些是线程不安全的?
  • 异常

  • 字符串

  • JVM

  • 并发与多线程

  • JDK

  • Spring

  • 设计模式

# ConcurrentHashMap线程安全

# 简要回答

  • ConcurrentHashMap在JDK1.7中使用数组+链表的结构,数组分为大数组Segment和小数组HashEntry。ConcurrentHashMap的线程安全主要通过Segment加ReentrantLock来实现的。
  • 在JDK1.8中,ConcurrentHashMap使用数组+链表+红黑树的结构,通过CAS操作或者synchronized来保证线程安全,缩小了锁的粒度,从而提高并发性能。

# 详细回答

  1. JDK1.7
    • ConcurrentHashMap分段锁技术:将数据分成一段一段存储,每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问,实现并发访问。
    • 对每个Segment独立加锁(继承ReentrantLock),小数组HashEntry用于存储键值对数据。ConcurrentHashMap包含Segment数组,Segment包含HashEntry数组,HashEntry是链表结构的元素,并发度为Segment的数量。
    • 读写操作:读操作无锁,volatile保证可见性,根据HashEntry的volatile字段保证读操作能获取最新值。写操作对目标加锁,完成后释放,同一Segment写操作互斥。
  2. JDK1.8
    • 通过CAS/局部synchronized实现线程安全。
    • ConcurrentHashMap对头结点加锁保证线程安全,锁粒度变小,发生冲突和加锁频率降低,并发操作性能提高。
    • 添加元素时:判断容器是否为空,如果为空利用CAS设置该节点;如果不为空则使用synchronized,遍历桶中的数据,替换或新增节点到桶中,最后再判断是否需要转为红黑树,这样可以保证并发访问时的线程安全。
    • 读写操作:读操作无锁,通过volatile保证可见性,支持并发读。写操作采用上述方法实现线程安全。

# 适用场景

版本 锁机制 优势
JDK1.7 分段锁Segment 实现简单,适合中等并发
JDK1.8 CAS+局部synchronized 锁粒度更高,高并发性能好

# 代码示例

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建ConcurrentHashMap实例
        ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();

        // 添加元素
        concurrentMap.put("apple", 10);
        concurrentMap.put("banana", 20);

        // 并发场景:启动两个线程同时操作map
        Thread thread1 = new Thread(() -> {
            // 原子操作:如果key不存在则添加
            concurrentMap.putIfAbsent("orange", 15);
            // 原子操作:更新值(将banana的数量加5)
            concurrentMap.computeIfPresent("banana", (k, v) -> v + 5);
        });

        Thread thread2 = new Thread(() -> {
            // 原子操作:获取并移除元素
            Integer value = concurrentMap.remove("apple");
            System.out.println("Thread2 移除了 apple: " + value);
        });

        // 启动线程
        thread1.start();
        thread2.start();
        // 等待线程执行完毕
        thread1.join();
        thread2.join();

        // 输出最终结果
        System.out.println("最终映射: " + concurrentMap);
    }
}
// 结果:
// Thread1 移除了 apple: 10
// 最终映射: {orange=15, banana=25}
// 若使用HashMap,可能会出现异常或者丢失数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 知识图解

  1. JDK1.7分段锁机制示意 image

# 知识扩展

  1. 面试官可能追问:
  • Q1:JDK1.7的分段锁为什么早在JDK1.8被弃用了?
    • 分段锁的并发读度会受到Segment数量限制(默认为16),高并发下仍有竞争。
    • 每个Segment是独立哈希表,包含数组、链表等,内存开销大。
    • JDK1.8使用更细粒度的锁,并发度理论上与数组长度相等,性能更好。
  • Q2:JDK1.8中synchronized锁的是什么?为什么不直接使用ReentrantLock?
    • 锁的是哈希桶数组中某个索引位置的头节点,仅影响当前冲突链,锁粒度极小。
    • synchronized在JDK1.6新增了锁升级机制后,性能与ReentrantLock基本相同,且更轻量级,更适合这种局部短时间锁定场景。
  • Q3:CAS操作具体会用在哪里?失败了会怎么样?
    • 用于初始化哈希桶,数组索引为空时插入第一个节点,修改节点值,标记扩容状态等无锁场景。
    • CAS失败时会自旋重试(有限次数),仍失败则退化为加锁,避免无限循环浪费CPU。
  • Q4:扩容时怎么保证线程安全?会不会丢失数据?
    • 扩容时通过volatile标记状态,多线程协同扩容时,每个线程负责迁移一部分哈希桶,通过CAS标记已迁移的索引,避免重复操作。
    • 写入操作会先协助完成扩容,再执行插入,确保数据不会丢失。
Last Updated: 3/10/2026, 6:08:48 PM

← Hash冲突的解决方案有哪些?HashMap是怎样解决Hash冲突的? 哪些集合类是线程安全的,哪些是线程不安全的? →

评论

验证登录状态...

侧边栏
夜间
卡码简历
代码随想录
卡码投递表🔥
2026群
添加客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记
鼓励/支持/赞赏Carl
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪