JDK7-HashMap线程不安全问题

630 阅读2分钟

我们都是知道HashMap是线程不安全的,那么它在多线程的情况下会产生什么样的问题呢?

JDK7线程不安全问题(死锁/数据丢失)

在JDK7中,HashMap容易出现线程不安全的问题点在于扩容时,创建新的数组,将旧数组中的数据重新进行hash,并将数据按照新的位置存放。

/**
 * 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);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

这段代码是HashMap的扩容操作,重新定位每个桶的下标,并采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转,这也是形成死循环的关键点。理解了头插法后再继续往下看是如何造成死循环以及数据丢失的。

下边是一个HashMap结构,其中存在hash冲突出现列表结构,1-2-3-4,现数组容量不足,将进行扩容:

未命名文件 (15).png 扩容后的正确结果为:

未命名文件 (16).png

假设现在有两个线程,A/B,线程A执行到newTable[i] = e;时CPU时间片耗尽被挂起,此时,e=2,next=3,e.next = NULL

未命名文件 (18).png

线程A的时间片消耗完之后,线程B开始执行,并最终扩容完成。并将数据同步到了主内存中

未命名文件 (16).png

此时线程A继续执行newTable[i] = e;结果为下图:

未命名文件 (19).png 随后继续执行下一轮循环迁移,此时3作为旧hashmap中的e,而在执行e.next时,从主内存中取到的结果是e.next= 2,此时3会继续按照头插法迁移到新的数组中去,因此新的一轮循环结果下来是旧的hashmap中,e=2

未命名文件 (20).png

再次执行循环,next = e.next = null表示执行完就结束了,继续进行数据迁移,把e=2迁移到新的数组中去,原本新hashmap中3->2,而本次循环执行的结果是2->3,此时就出现了相互引用,死循环了,而且其中数据4也丢失了。

未命名文件 (21).png