版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Hzt_fighting_up/article/details/78737468
最近在学习并发,看到书上写到hashmap在并发执行put操作时会引起死循环,因为在put中会引起扩容操作,使链表形成环形的数据结构,不是很明白,然后在网上看了一些博客,但是博客都是jdk1.7版本的,而1.8版本中的扩容操作已经和1.7版本中大不一样了,于是自己开始研究,看源码的时候,觉得jdk1.8版本中多线程put不会在出现死循环问题了,只有可能出现数据丢失的情况,因为1.8版本中,会将原来的链表结构保存在节点e中,然后依次遍历e,根据hash&n是否等于0,分成两条支链,保存在新数组中。jdk1.7版本中,扩容过程中会新数组会和原来的数组有指针引用关系,所以将引起死循环问题。
jdk1.8扩容代码
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
}
...省略部分代码
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//遍历旧数组中的元素,复制到table数组中
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;
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;
}
.
测试代码1:
public class TestHashMap {
private static HashMap< Integer, Integer > map = new HashMap<>(2);
public static void main(String[] args) throws InterruptedException {
//线程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100000; i++) {
int result = i;
new Thread(new Runnable() {
@Override
public void run() {
map.put(result, result);
}
}, "ftf" + i).start();
}
}
});
t1.start();
//让主线程睡眠5秒,保证线程1和线程2执行完毕
Thread.sleep(5000);
for (int i= 1; i <= 100000; i++) {
//检测数据是否发生丢失
Integer value = map.get(i);
if (value==null) {
System.out.println(i + "数据丢失");
}
}
System.out.println("end...");
}
}
此时插入100000条数据,没有引起死循环和数据丢失
继续增大数据量:
此时插入100000条数据,没有引起死循环和数据丢失
继续增大数据量:数据增加到1000000,出现java.lang.OutOfMemoryError,栈内存溢出,重新调整jvm栈区内存的大小
如何调整:深入理解jvm书中写到,如果是建立过多线程导致内存溢出,在不能减少线程数量或者更换64位虚拟机的情况下,只能通过减少最大堆和减少栈容量来换取更多的线程。
在idea中修改jvm参数:-Xss120k,减少一个线程分配的栈内存(默认为1m)
调整以后,成勋正常运行,并且出现数据丢失现象,但是仍没有出现死循环现象
运行结果部分:
value:null数据丢失
value:null数据丢失
value:null数据丢失
value:null数据丢失
value:null数据丢失
end...
出现了很多null值,这里只是一部分
以上仅是lz自己的观点,有错误欢迎指导讨论