https://www.jianshu.com/p/1e9cf0ac07f4
应用中碰到使用linkedhashmap出现了循环指向导致的线程遍历死循环的问题,linkedhashmap继承自hashmap,从网上查询了一下相关的文章,大都说的比较一致。 但是个人感觉抽象理解起来比较困难,虽然介绍已经非常详细了,遂自己总结了一下
大概的逻辑如下,详细的可以看上面的链接。
死循环现象主要出现在多线程并发使用hashmap进行put操作时。
多线程在put时都进入到put方法,并且由于hashmap内部自身entry数量不够需要进行扩容,在扩容的核心方法transform主要是对于new newEntry[oldEntrySize*2]; 然后将旧Entry数组中的链表数据rehash获取新的new_hash_index放入到新的entry中。
由于旧entry[]数组中的每个元素的值是一个链表的头指针,并不是单个元素。需要遍历每个指向的元素重新计算hash值并放入到新的entry[hash_index]中,当同一个链表中的元素重新计算hash后的数组下标仍然相同时,hashmap扩容后会出现在旧entry[i]链表中的链表方向与新扩容后链表的方向相反。
这是由于线程需要遍历每个链表中的元素重新计算hash值,因此会导致原来的链表头在新链表中变成链尾,原来的尾部元素在新链表中变成头指针并被保存在newEntry[hash_index]中。
而正是由于遍历过程中链头链尾反转链接的操作过程,导致在多线程执行环境下容易产生环链,最终查询时出现死循环。
自己把hashmap扩容方法transform核心代码摘取下来模拟了一下环链过程。并画了一个二维表执行过程,从开始到结束两个线程是如何形成环链的,加深自己的理解。
class LinkEntry {
char value;
LinkEntry next;
LinkEntry(char value, LinkEntry next) {
this.value = value;
this.next = next;
}
}
static LinkEntry reverse(LinkEntry entry) {
LinkEntry head = null;
LinkEntry next = null;
LinkEntry tmp = entry;
while (tmp != null) {
next = tmp.next;
tmp.next = head;
head = tmp;
tmp = next;
}
}
测试数据集结构
a->b->c
核心流程
代码 |
tmp |
next |
head |
|
tmp |
next |
head |
while (tmp != null) |
a->b->c |
null |
null |
a->b->c |
null |
null</ |