聊一聊JDK 7的HashMap中的“死锁”是怎么回事?


    HashMap是线程不安全的,在HashMap的源码中并未对其操作进行同步执行,所以在并发访问的时候就会出现线程安全的问题。

    由于上一篇的ConcurrentHashMap篇中讲到了死锁,也画了图,但是很多读者说看不懂,这里我的锅,在这里详细的进行图解。

    假设:有线程A和线程B,并发访问HashMap中的数据。假设HashMap的长度为2(这里只是为了讲解方便假设长度为2),链表的结构图如下所示:

    聊一聊JDK 7的HashMap中的“死锁”是怎么回事? - 图1

    4和8都位于同一条链表上,其中的threshold为1,现在线程A和线程B都要进行put操作,首先线程A进行插入值。

    此时,线程A执行到transfer函数中(transfer函数是resize扩容方法中调用的另一个方法),当执行(1)位置的时候,如下所示:

    1. /**
    2. * Transfers all entries from current table to newTable.
    3. */
    4. void transfer(Entry[] newTable, boolean rehash) {
    5. int newCapacity = newTable.length;
    6. for (Entry<K,V> e : table) {
    7. while(null != e) {
    8. Entry<K,V> next = e.next; ---------------------(1)
    9. if (rehash) {
    10. e.hash = null == e.key ? 0 : hash(e.key);
    11. }
    12. int i = indexFor(e.hash, newCapacity);
    13. e.next = newTable[i];
    14. newTable[i] = e;
    15. e = next;
    16. } // while
    17. }
    18. }

    此时线程A挂起,在此时在线程A的栈中就会存在如下值:

    1. e = 4
    2. next = 8

    此时线程B执行put的操作,并发现在进行put操作的时候需要扩容,当线程B执行 transfer函数中的while循环,即会把原来的table变成新一table(线程B自己的栈中),再写入到内存中。

    执行的过程如下图所示(假设两个元素在新的hash函数下也会映射到同一个位置):

    聊一聊JDK 7的HashMap中的“死锁”是怎么回事? - 图2

    此时线程A有获取到cpu的执行时间,接着执行(但是纤层A中的数据仍是旧表数据),即从transfer代码(1)处接着执行,当前的 e = 4, next = 8, 上面已经描述,执行的的过程若下图所示:

    聊一聊JDK 7的HashMap中的“死锁”是怎么回事? - 图3

    当操作完成,执行查找时,会陷入死循环!