hashMap 线程不安全源码分析-笔记

多线程情况下,HashMap线程不安全环节源码分析:

1、首先需要了解一下JDK7中HashMap的存储结构(图片来自某大神博客吐舌头):


2、多线程情况下使用hashMap的put方法,源码如下

 //向hashMap中添加Entry
    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);//扩容2倍
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
  /**
     * @param hash 通过key值计算出的值
     * @param key 
     * @param value
     * @param bucketIndex 通过hash值计算出的table中的entry数组的位置
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];//保存之前该位置的entry值
        table[bucketIndex] = new Entry<>(hash, key, value, e);//new一个新的entry挂在table中相同位置的entry的next上
        size++;
    }

假设线程A、B同时向hashMap中添加元素,根据源码可以看出每次添加一个新的元素都会创建一个新的Entry,根据源码可以看出,每次创建新的entry的时候都会根据key值计算出hash值,然后根据hash值计算出bucketIndex值。如果两个线程同时传入的数据计算出的hash值相同,就会导致其中一个线程的数据丢失。

3、hashMap扩容

hashMap扩容机制:创建一个新的table设置容量为你定义的容量,然后通过transfer方法给newtable赋值。transfer方法赋值过程是,首先遍历之前的table,根据之前table中的各个entry中hash值和newTable的容量重新计算entry在newTable中的位置,完成扩容。

多线程情况下,扩容机制线程不安全:

情境一:假设有两个线程A,B同时访问同一个HashMap并同时进入resize方法,线程A根据自己的需要重新hash计算了各个entry在table中的位置,线程B根据自己需要做了同样的操作,那么只有最后一个访问的线程的扩容操作生效,其余线程的数据会丢失。

情境二:假设线程A,B同时访问同一个HashMap并同时进入resize方法,对于每个线程在任意语句都可能被挂起,待其他线程执行执行若干语句后再被调度,线程A在执行resize的时候挂起,在次过程中线程B完成了resize,table结构中可能会出现链表环,一旦使用get读取数据就会出现死循环。

  void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {//判断当前table的容量是否为最大容量,如果是则不进行扩容
            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);
    }
    /**
     * 
     * @param newTable 新的table数组
     * @param rehash 是否重新计算entry的hash值,返回boolean类型数据
     */
    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;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);//根据之前的hash值重新计算entry变量存放在new table中的位置
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

高并发情况下最好使用concurrentHashMap,或者使用Collections.synchronizedMap包装一下hashMap

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页