1.聊聊ConcurrentHashMap 、源码,实现原理
- 在Java 1.8 中数据结构是 数组+链表+红黑树。
- 每个数据单元都是一个Node结构,结构中有Key ,value next ,hash 这几个属性。
- next 字段是在Hash冲突后形成链表需要的。
2.ConcurrentHashMap并发能力为什么好于Hashtable
① Hashtable是通过对hash表整体进行锁定,使用synchronized来保证线程安全,是阻塞式的,当一个线程占有这个锁时,其他线程必须阻塞等待其释放锁
ConcurrentHashMap实现如下:
② jdk1.6的实现:ConcurrentHashMap是采用Segment分段锁的方式 其中 Segment 继承于 ReentrantLock,它并没有对整个数据结构进行锁定,而是局部锁定。如果容量大小是16,那个他的并发度就是16,相当于效率提高了16倍。
③ jdk1.8的实现:采用CAS + Synchronized,synchronized在put的时候锁是节点的第一个元素,但其底层还是“数组+链表+红黑树”的实现。当链表长度大于8并且数组长度大于64的时候转红黑树。
ps补充: CAS 获取数据时不加锁,通过volatile实现
2. sizeCtl 属性的含义
- sizeCtl=0 默认状态,表示数组还没有被初始化。
- sizeCtl =-1 表示正在初始化
- 初始化散列表的时候使用,当多个线程同时执行到initTable逻辑的时候,使用CSA 修改 sizeCtl这个的值。
- 期望值,初始值是0,更新成功后是-1 。
- 并发下只有一个成功
- CAS失败的线程会进行一个自旋检查,检查这个table是否被初始化出来了。
- 每次自旋检查后会有短暂的释放所占用CPU
- sizeCtl >0 表示下次触发扩容的阈值。
- 比如等于12,则put后检查这个容量,当发现> =12就会触发扩容操作
- sizeCtl <0 && sizeCtl !=-1 代表什么情况
- 散列表正在扩容状态,高16位表示扩容表示戳,低16位表示参与扩容的工作线程数量 +1
3.如何保证线程安全
- 若位置上是空的,添加新Node结点 ,通过Cas保证,
- 成功就返回,若失败则表示被其它线程竞争到了
- 则当前线程需要重新执行逻辑,再次尝试处理。
- 若是已经存在Node节点非空,使用synchronized锁头结点
4. hash 寻址算法
- 首先拿到 key 的 hashcode 然后再进行一个扰动运算。
- 让hashCode 的高16位,与低16位进行一个异或运算。
- 将符号位强制设置为0,让整个HASH值成为一个正数,
- 最终是 >=0 <= (table.length-1)
5.ConcurrentHashMap为什么不采用 AtomicLong 统计数量
- 性能问题。
- CAS 比较替换,如果期望值与内存值是一致的再执行替换操作,并且CSA 反应到内核层面
- 其实是cmpxchg 指令 ,当这个指令执行的时候会检测当前平台是否为多核平台
- 如果是多核的话,cmpxchg 会通过锁总线,的形式来保证同一时刻,只能有一个cpu执行。
- 反应到底层仍然是串行,高并发下,陪跑的线程太多,损耗CPU
6.LongAdder 如何解决了大并发下,性能问题?
分段锁的概念使用CAS锁数组的一个格
7.size()方法是怎么实现的?
- 最核心的是 volatile 修饰 Long类型的 baseCount 字段,+ CounterCell 数组,
- 数组内是一个 volatile long value
- 若从来没有产生并发失败(CAS修改baseCount 失败)
- 数据会全部累计到baseCount字段 ,采用Cas更新baseCount字段
- 当某个线程冲突,cas 修改baseCount字段冲突的时候,就将cell [] 数组数据架构构建
- 之后的累计请求,就不在首选baseCount字段了,而是根据分配给线程的hash值,进行位于运算,找到对应的cell[]
- 将累加的值,通过cas方式写入到cell里。
8.longAdder 的内部结构
- 最核心的是Long类型的Base字段,+ cell 数组
- Cell 结构里面有一个volatile Long类型的Value 字段
- 使用longAdder的过程中,若从来没有产生并发失败(CAS修改base 失败)
- 数据会全部累计到base字段 ,采用Cas更新Base字段
- 当某个线程冲突,cas 修改base字段冲突的时候,就将cell [] 数组数据架构构建
- 之后的累计请求,就不在首选base字段了,而是根据分配给线程的hash值,进行位于运算,找到对应的cell[]
- 将累加的值,通过cas方式写入到cell里。
9.ConcurrentHashMap 进行 put 的步骤
- 首先对 Key求Hash值。计算所在数组下标。
- 判断数组table是否为空。为空则进行初始化。
- 若没有Hash碰撞 ,则调用CAS插入相应的数据
- 若存在碰撞,则对该节点加synchronized锁,锁的是节点的第一个元素,遍历链表插入新节点
- 若链表长度>8(put第9个的时候)则调用转红黑树的方法
- 内部判断若数组<64 则进仅行扩容,若>= 64 则转成红黑树。
- 若匹配到旧key ,值不为空,则返回旧值,本次操作是更新操作。