【面经】HashMap和ConcurrentHashMap区别
HashMap和ConcurrentHashMap区别
HashMap和ConcurrentHashMap的最大区别在于线程安全性。
HashMap是非线程安全的。这意味着在多线程环境下,如果多个线程同时修改
HashMap,可能会导致数据不一致,甚至引发死循环等问题。ConcurrentHashMap是线程安全的,它通过细粒度的锁机制来保证并发安全。在多线程环境下,不同的线程可以同时对不同的部分进行修改而不会影响到其他线程。
ConcurrentHashMap的线程安全是通过分段锁(Segment Locks)来实现的。具体来说,ConcurrentHashMap会将Map分为多个段,每个段有自己的锁,这样线程可以在不同的段上进行并发操作,不会发生阻塞。相比之下,HashMap不进行任何的锁机制,因此不支持并发修改。
JDK1.7 版本底层是 数组+链表 实现,使用分段锁来保证线程安全,它默认是将数组分成了16段,同时给每一段来配一把锁,因此最多只有16个线程去同时操作集合不同段。这样降低了并发操作时的锁竞争,提高并发性能。
JDK1.8 版本底层是数组+链表+红黑树,在并发处理方面去掉了分段锁的设计,而是使用 CAS + synchronized 来实现一种更加细粒度的锁。
从源码上看:添加元素时首先会判断数组是否为空,如果数组为空则使用CAS 来初始化数组。如果数组不为空则判断元素要存入的桶位置上是否为空,如果桶位置上为空则使用CAS 设置桶上的头节点;如果桶位置上不为空则使用synchronize 加锁,锁住桶位置上的头节点,遍历桶中的数据,替换或新增节点到桶中,并且再判断链表是否需要转为红黑树
锁粒度细化:锁的粒度从段缩小到单个桶(链表/树的头节点),减少竞争概率。
JDK 1.8 的 ConcurrentHashMap 设计体现了无锁化编程和锁粒度细化的优化思想,是 Java 并发容器的经典实现。
为什么不全部用CAS或者全部用synchronized加锁实现线程安全,为什么不同操作用不同的方式去保证线程安全
我们可以看不同的方式中做的事情是什么:
- CAS保证线程安全时,它做的事情是初始化数组或者初始化头节点
- synchronized保证线程安全时,它做的事情是遍历桶下的结点,比较key是否相等,然后再插入元素或者替换元素,再判断链表是否转为红黑树
所以可以看出是后者耗时更长
- CAS无锁就适合做短时间的任务,因为如果任务执行时间长,就会有线程不断的自旋尝试,过度占用CPU
- synchronized就适合做长时间、高竞争的任务,加锁后,其余线程会进入休眠,不会占用CPU
- 这是两者的特点不同,适用的场景不同。
这种设计体现了对不同场景的精细化控制,是现代并发编程中“无锁优先,锁为补充”思想的典型实践。
