HashMap源码解析1.8
new HashMap()创建一个空的map
put值时 resize() 初始化容量16 160.75=12(threshold) 超过12扩容,16<<1(162^1)=32, threshold=32*0.75=24
HashMap 允许 key 中有 一个null 值, HashTable 是不允许的,ConcurrentHashMap也是不允许的(无法分辨是key没找到的null还是有key值为null)。这样的好处就是可以给一个默认值
hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) 异或
index = i = (n - 1) & hash 也就等于 hash % n(容量大小) 与
相比于put()方法,get()方法的实现就相对简单多了。主要分为两步,先是通过key的hash值计算目标bucket的下标,然后遍历对应bucket上的链表,逐个对比,得到结果
还是一样的存储套路,先根据key确定在哈希table中的下标,找到对应的bucket,遍历链表(或红黑树),做插入操作。在JDK7中,新增结点是使用头插法(引起死锁),但在JDK8中,在链表使用尾插法,将待新增结点追加到链表末尾
步骤①:若哈希table为null,或长度为0,则做一次扩容操作;
步骤②:根据index找到目标bucket后,若当前bucket上没有结点,那么直接新增一个结点,赋值给该bucket;
步骤③:若当前bucket上有链表,且头结点就匹配,那么直接做替换即可;
步骤④:若当前bucket上的是树结构,则转为红黑树的插入操作;
步骤⑤:若步骤①、②、③、④都不成立,则对链表做遍历操作。
a) 若链表中有结点匹配,则做value替换;
b)若没有结点匹配,则在链表末尾追加。同时,执行以下操作:
i) 若链表长度大于TREEIFY_THRESHOLD(8)(如果数组长度没有达到64,优先扩容解决),则执行红黑树转换操作;
ii) 若条件i) 不成立,则执行扩容resize()操作。
以上5步都执行完后,再看当前Map中存储的k-v对的数量是否超出了threshold,若超出,还需再次扩容。
当红黑树节点<UNTREEIFY_THRESHOLD(6),会缩容,转换为链表
resize()会重新hash,很耗时,尽量避免
ArrayList1.8
new ArrayList1()创建一个空的数组
add值时才扩容为10
每次扩容 int newCapacity = oldCapacity + (oldCapacity >> 1);就是乘1.5
然后copy数组到新数组:elementData = Arrays.copyOf(elementData, newCapacity);
HashSet
底层就是一个HashMap,元素不能重复
ConcurrentHashMap
table数组元素作为锁,从而实现了对每一行数据进行加锁,并发控制使用Synchronized和CAS来操作
多个线程又是如何同步处理的
在ConcurrentHashMap中,同步处理主要是通过Synchronized和unsafe两种方式来完成的。
·在取得sizeCtl、某个位置的Node的时候,使用的都是unsafe的方法,来达到并发安全的目的
·当需要在某个位置设置节点的时候,则会通过Synchronized的同步机制来锁定该位置的节点。
·在数组扩容的时候,则通过处理的步长和fwd节点来达到并发安全的目的,通过设置hash值为MOVED
·当把某个位置的节点复制到扩张后的table的时候,也通过Synchronized的同步机制来保证现程安全
从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树,总结如下:
1、JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8实现降低锁的粒度就是HashEntry(首节点)
2、JDK1.8版本的数据结构变得更加简单,去掉了Segment这种数据结构,使用synchronized来进行同步锁粒度降低,所以不需要分段锁的概念,实现的复杂度也增加了
3、JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档
4、JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock:
- 低粒度加锁方式,synchronized并不比ReentrantLock差,
粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了 - JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然
- 在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存
ConcurrentHashMap 对单个Node加上同步锁
CopyOnWriteArrayList
使用写时复制策略保证list的一致性,而获取–修改–写入三个步骤不是原子性,所以需要一个独占锁保证修改数据时只有一个线程能够进行。另外,CopyOnWriteArrayList提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对list的修改是不可见的,迭代器遍历的数组是一个快照。
CopyOnWriteArrayList
add(),remove()通过ReentrantLock加锁操作,获取当前Array,复制一份,再写入,写入完成,把新写入的数组设置为Array
get()不变,支持并发