分析
上一篇文章我们详细分析了一遍hashMap的数据结构,但是我们知道,在多线程情况下。hashmap是不安全的,这是问什么呢?我们首先分析一下:
多线程在对HashMap进行put的时候,邹游一个时候,hashCode会超过负载因子(默认0.75),超过以后,HashMap内部就会对数组扩容,扩容的时候会调用resize().下面我们看一下resize()方法:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next; //这一步,获取了e的下一个节点
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
我们假设有这样的一个情况存在:
此时,有线程a,b分别往HashMap里面put,a已经得到A的next是B,刚好到此时,Enty数组长度达到了12,需要扩容。而扩容后,需要从新计算hashcode,以便让hash值更加均匀的分布在hash桶上。而刚好,b计算出来了B的next是A,而此时便会出现一个环形,导致死循环!
而ConcurrentHashMap使用的是分段锁技术,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。如下
..]
Segment 内部是由 数组+链表 组成的。
简单分析一下java 8 中的ConcurrentHashMap的put方法:
java 8 的ConcurrentHashMap采用的是懒加载的方式,只有当调用put方法的,map没有被实例化的时候才会将map实例化。然后通过key.hash计算出桶存在的位置,采用CAS的方法将数据put上去。
解释一下CAS :
V:需要更新的值
E:期望的值
N:最后要改成的值
当V=E则将N赋值给V,否则说明数据被更改过了,放弃操作
下面这张图是java 8 的 ConcurrentHashMap 数据结构。可以看到,table[] 里面的数据结构不仅仅是java 7 的entry了,有node,treeNode,ForwardingNode,ReservationNode等...node可以理解成以前的entry,而后面几个实际上都继承自node,但是都有各自的含义,比如treeNode表示这是一个链表或者是红黑树(ps:之所以将链表转换成红黑树,是因为链表的时间复杂度是O(n),而红黑树是一个平衡二叉树,时间复杂度是O(logn))
https://upload-images.jianshu...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。