可能这样的面试场景比较常见:
面试官:ConcurrentHashMap是怎么实现的呢?
程序猿小僧:ConcurrentHashMap是线程安全的HashMap,基于分段锁实现,
面试官:分段锁又是怎么实现的呢?
程序员小僧:这个。。。
进入正题,要说ConcurrentHashMap的实现,那得从两个JDK版本说起!
JDK1.7的实现
ConcurrentHashMap的数据结构是由一个HashEntry数组和多个Segment组成,主要实现原理是通过锁分离的思路解决了多线程的安全问题,如下图所示:
这里简单总结下:
1、通过两次hash算法将Entry数组最终存储在段(Segment)中的数组活链表里,Segment的Size永远是2的幂,因为在计算Segment的Size时使用了左移运算符<<
2、由于Segment继承ReentrantLock,所以在put时通过ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式(自旋就是一个循环获取锁的过程)继续调用tryLock()方法去获取锁,超过指定次数就挂起,等待唤醒.
3、get操作跟HashMap类似,只是ConcurrentHashMap第一次需要经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry,遍历该HashEntry下的链表进行对比,成功就返回,不成功就返回null
JDK1.8的实现
没有继续使用Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,虽然在JDK1.8中还能看到Segment,它只是为了兼容旧版本而已.
1、锁的粒度降低为一个Entry,而不是1.7中的多个Entry组成的Segment,
2、当链表长度大于8时,使用红黑树来优化链表,以达到O(logn)的遍历复杂度
3、由于Synchronized在1.6被优化成了一个锁粒度的升级(自旋锁->偏向锁->轻量级锁->重量级锁),而非重量级锁,从而提高并发的效率。