关于 JDK 1.7 HashMap 并发情况下可能发生的死循环与数据丢失

这是一个老生常谈的内容了,最近复习发现博客中居然没有记录这块,今天特地记录下。

 HashMap 本身是线程不安全的,如果线程并行插入元素,可能会同时触发扩容。这里会新建一个更大的数组,并调用 transfer 方法对元素进行转移,转移的逻辑也很很好理解,就是遍历原来 table 中每个位置的节点,并对每个元素进行重新 hash,在新的 newTable 找到位置,并插入。

transfer 方法如下:

 /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                //先保存好下一个节点,因为当前节点就要被处理了,线程二走到了这一步,保存好了下一个节点,这时候线程一进行操作
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
             //最开始newTable[i];是null
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

就是说里面有一个类似链表倒排的过程:

也就是在原来数组中是一个table里面是,k1,k2,k3,在新数组中就是 k3,k2,k1,使用的头插法

正常来的扩容(便于理解和模拟,假定新的元素 rehash 后都在同一个数组位置):

之前的结构:

扩容后:

也就是倒过来了。

接下来分析链表出现环的过程。这里说一种情况,比如线程二走到了这一步:

然后线程一开始执行 transfer 方法,并且执行完成,那么此时的内存分配为:

注意线程二的 e=k1,next=k2,接下来线程二执行第一个循环:

第一个循环最后是 e=next=k2。

线程二再执行第二个循环,要注意此时 e=k2,next = e.next = k1:

线程二执行第二个循环:

目前看着没啥问题,第二个循环走到最后一步就是 e=next=k1,接下来线程二再走一次循环,此时要处理 k1。那么就会出现:

即出现了循环链表。

最终线程一或者线程二总有一个会将自己的 newTable 进行替换。

首先无论最终是线程一还是线程二的 newTable 进行了替换。假如我现在要进行 get 操作,刚好是到我们处理的那个数组位置。就会出现遍历环形链表,就会出现了死循环。

假如是线程二的 newTable 替换了,那 k3 数据就会被丢失。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值