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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值