天天看点

jdk1.7中hashMap死锁详解死锁发生的条件分析

死锁发生的条件

在hashMap扩容时,且在多线程访问时可能发生死锁。

分析

  1. 生成一个hashMap的初始table。
public static void main(String[] args) {
        // 测试 java 7 中哪些数字的 hash 结果相等
        System.out.println("长度为16时,桶下标为1的key");
        for (int i = 0; i < 64; i++) {
            if (hash(i) % 16 == 1) {
                System.out.println(i);
            }
        }
        System.out.println("长度为32时,桶下标为1的key");
        for (int i = 0; i < 64; i++) {
            if (hash(i) % 32 == 1) {
                System.out.println(i);
            }
        }
        // 1, 35, 16, 50 当大小为16时,它们在一个桶内
        final HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        // 放 12 个元素
        map.put(2, null);
        map.put(3, null);
        map.put(4, null);
        map.put(5, null);
        map.put(6, null);
        map.put(7, null);
        map.put(8, null);
        map.put(9, null);
        map.put(10, null);
        //因为链表采用头插法,所以下面三个节点为table[1]的前三个节点
        map.put(16, null);
        map.put(35, null);
        map.put(1, null);
        System.out.println("扩容前大小[main]:"+map.size());
        new Thread() {
            @Override
            public void run() {
                // 放第 13 个元素, 发生扩容
                map.put(50, null);
                System.out.println("扩容后大小[Thread-0]:"+map.size());
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                // 放第 13 个元素, 发生扩容
                map.put(50, null);
                System.out.println("扩容后大小[Thread-1]:"+map.size());
            }
        }.start();
    }
    //hashMap中的哈希函数,用来生成hash值
    final static int hash(Object k) {
        int h = 0;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
           
  1. 主要分析table[1]位置,如下图。
    jdk1.7中hashMap死锁详解死锁发生的条件分析
  2. 如果再插入<50,null>键值对,则数组扩容,将键值对重新分配。源码如下,死锁主要发生在这里。
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;
            }
        }
    }
           
  1. 两个线程同时运行,线程0和线程1同时运行到 if(rehash){ 这一行。e和next 的指向如下图。
    jdk1.7中hashMap死锁详解死锁发生的条件分析
  2. 线程0停止,线程1运行完,数组扩容后,键值对会进行重排,如下图。
    jdk1.7中hashMap死锁详解死锁发生的条件分析
  3. 线程0和线程1都是访问的相同的节点,所以当线程1运行完后线程0的指向如下。e还是指向1,next还是指向35。但是线程1将链表的结构改变了。
    jdk1.7中hashMap死锁详解死锁发生的条件分析
  4. 对比线程0先后两种状态,如果线程0继续运行,则会发生死循环。
    jdk1.7中hashMap死锁详解死锁发生的条件分析

继续阅读