环形链表
多线程并发会形成环形链表,这是在jdk1.7下会发生
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
// B线程执行到这里之后就暂停了
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
单线程扩容图解
第一次循环
- 第一次循环:Entry
next = e.next; 因为for循环遍历,所以链表头就是变量e;同时执行Entry next = e.next; 图形为:
- 头插法,转移key=3的节点
- 第一: e.next = newTable[7];将3和7断开连接
- 第二: newTable[7] = e; 把e变量指向连接到新数组上
- 第三 : e = next; 则e对象赋值为7 entry,e变量指向next
第二次循环
- Entry
next = e.next; 此时e其实指向的是7这个节点,由于7的next是null,next则为null。(延用第一次循环后的图)
- e.next = newTable[i]; 就是把e的next节点指向新数组中那个3的节点。则 e.next=key(3);
- newTable[i] = e;新数组的那个位置直接不指向原来的3节点而是指向7节点。e = next;把e节点与7节点断链。
多线程图解
此时有A,B两个线程。A线程执行到图中这块就停止了,B线程做一次扩容
线程B扩容时,节点的变化
图解如下图: A线程停下后,对应的entry元素如下图。由于线程是隔离的,读到的变量其实还是之前一开始没有变的值。
此时,A停,B线程重头开始执行,完成完成的单线程扩容过程!实现新数组的扩容重排!此时,线程B扩容完成
线程A接着扩容
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
// B线程执行到这里之后就暂停了
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
此时A线程开始执行:此时,entry节点属性已经发生了变化,但是线程A线程的局部变量中的e和next的指针是没有变的
此时:开始执行那个循环
第一层循环
第一步: e.next = newTable[i];
第二步: newTable[i] = e;
第三步: e = next;
第二层循环
Entry
此时,已经看出了问题出现了,next变量,又指回了3 entry节点;
第一步: e.next = newTable[i]; 就是将3节点指向新数组那个节点上,其实这里没有什么变动和原来一样。
第二步: newTable[i] = e;把7节点挂到新数组上
第三步: e = next; e指向3节点
到此其实就应该完成转换了,万万没想到还有while判断要执行呢,此时e != null是成立的,因此就又会循环一次
Entry
第一步: e.next = newTable[i];
第二步: newTable[i] = e;
第三步: e = next;
此刻, 死循环出现了!!
第二步: newTable[i] = e;
第三步: e = next;
总结:
多线程的问题就是在于并发情况下指针关系没有处理好,就是因为这两个指针指向的节点的next信息其实都有变化了,而下一个线程还是按照原来的头插法去插入值,这个时候就会出现链表循环的问题。
环形链表造成死循环和数据丢失问题
死循环
假如说,要是来get一个值,get(k5),k5的hash取模算法会定位到那个环形链表的位置,然后开始遍历,随后就遍历到环形链表的节点了,而且因为环形链表里可能没有k5的值,正常情况下是要继续往下遍历吧。可是有两个节点成环了就就不停的在这两个节点之间遍历,无法往下面遍历了。
数据丢失
如果是线程1的newTable赋值给了hashmap里的table,采用了线程1的newTable之后,就会导致说,此时会导致