JDK7.0 HashMap源码浅析

990 阅读3分钟

1、先来看一个问题

Q:1.7有什么特点,在什么情况下会造成死锁

先从put的过程看起

	 public V put(K key, V value) {
			if (table == EMPTY_TABLE) {
				inflateTable(threshold);//初始化链表
			}
			if (key == null)
				return putForNullKey(value);//插入key为null的节点
			int hash = hash(key);//计算hash值
			int i = indexFor(hash, table.length);//由hash计算index
			for (Entry<K,V> e = table[i]; e != null; e = e.next) {
				
				Object k;
				//遍历找到key相同的节点
				if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
					V oldValue = e.value;
					e.value = value;
					e.recordAccess(this);
					return oldValue;
				}
			}

			modCount++;//修改次数+1,用于迭代器
			addEntry(hash, key, value, i);
			return null;
		}
	void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
	
	   private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);//找到大于toSize的最近的2次幂

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }
	
	/**
     * Initialize the hashing mask value. We defer initialization until we
     * really need it.
     */
    final boolean initHashSeedAsNeeded(int capacity) {
		//刚进来的时候hashSeed = 0,所以currentAltHashing=false
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;//current false
		//相当于switching=useAltHashing
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }
	
	 final int hash(Object k) {
        int h = hashSeed;//hashSeed
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);//如果capacity
			//超过了ALTERNATIVE_HASHING_THRESHOLD(即"jdk.map.althashing.threshold"设置的值,
			//一般不会超过,因为是Integer的max值,只有设置了这个,才有可能超过)
			//超过的话,则启用Hashing.murmur3_32();哈希函数来执行散列任务(一般只执行一次,下次就返回
			//执行后的值)
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
	
	private static volatile boolean booted = false;
	 public static boolean isBooted() {
        return booted;//返回的是true,不知道是在哪里被赋值了
    }
	//扩容
	void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
     /**
	扩容移动数组的时候,多线程容易产生死循环,单线程则不会
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {//遍历table数组
            while(null != e) {
                Entry<K,V> next = e.next;//线程2跑到这里被阻塞
				//等到线程1扩容完毕,(会产生一个位置的倒置)
				//线程2的e和next是
				//指向线程1扩容之后的新指针
				//从而会使得线程2有可能出现环,致使造成死循环的情况
                if (rehash) {//一般是false,具体请看@initHashSeedAsNeeded的解析
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                // 线程1进行完之后,变成了b->a
                // 但是线程2的e和next还是a->b的顺序
                // 又由于1.7采取的是头插法,
                // 这就导致了执行到第3次的时候b的next指向的是a
                e.next = newTable[i];//线程2
                newTable[i] = e;
                e = next;
            }
        }
    }

总结

在transfer扩容的时候,由于1.7是采用头插法,所以在多线程的情况下,容易造成环的形成,也就是死锁的由来.