锁
1.8 之前:
使用Segment(分段锁,它继承了ReentrantLock ReentrantLock 是AQS(抽象队列同步框架) 的独占锁模式)
ReentrantLock实现模式
一个数组分为多个段落,然后每个段落对应一个资源state(int类型 初始为0加锁 +1 释放锁-1 等于0则代表没有被占用),线程并发的时候每个线程会封装成一个node节点进入aqs阻塞队列 队列首节点判断state是否为0 ,为0则+1执行难此资源,如果是独占模式则是等首节点执行完后唤醒后续节点进行获取资源操作;
共享模式如下:不是首节点的会判断自己node.pre是否为首节点如果是则casAndSwap 资源 如果node.pre不是首节点则自旋;
缺点:
1、分段后加锁不连续会导致碎片化,内存资源浪费
2、对象创建后分段数量不变,当进行扩容时,每段大小增加导致锁的粒度增大导致并发能力减弱
3、使用ReentrantLock则需要节点继承AQS来获得同步支持,增加内存开销
1.8 之后:
使用synchronized+cas那么synchronized和cas分别用在了什么地方呢?
synchronized用在锁住数组桶内链表头节点上
cas用在初始化桶中第一个元素的时候会进行cas
数据结构
1.8 之前:
数组+链表
1.8 之后:
数组+链表+红黑树
当数组长度大于60的时候链表长度大于8的时候会变为红黑树
扩容
1.8 之前:
数组默认16 ;当存入数据大小大于16*负载因子(0.75)=12的时候会进行扩容,扩容俩倍,之所以是2倍是因为可以让hashcode更均匀分散
1.8 之后:
数组默认16 ;当存入数据大小大于16*负载因子(0.75)=12的时候会进行扩容,扩容俩倍,之所以是2倍是因为可以让hashcode更均匀分散
链表插入方式
1.8 之前:头插法
并发情况会导致环形链表:
线程一:读取到当前的hashmap情况,在准备扩容时,线程二介入
线程二:读取hashmap,进行扩容
线程一:继续执行
简单来说就是头插法情况下 a>b数组在 1线程进行扩容还没有开始此时cpu调度到了 2线程 然后取值到新数组变成了a先到后b到然后因为头插法 将b插入到了a前面 此时变为b>a 然后 cpu继续调度 1线程 发现b>a 然后自己内存的事a>b 然后就变成了b>a>b
jdk 1.8之后
使用尾插法不会出现环形链表