为什么hashmap是非线程安全的?

严格意义上来说,hashmap的时间复杂度并不是O(1),具体原因看get函数;此外,当在hashmap里面存的内容过多时,重新resize hashmap的长度,这也是一个耗时的时间。

 

首先看看hashmap的代码:

    static final int DEFAULT_INITIAL_CAPACITY = 16这个是hasmmap默认的大小。

    static final int MAXIMUM_CAPACITY =1 << 30 hashmap最大的容量,也就是2的幂30。

    static final floatDEFAULT_LOAD_FACTOR = 0.75f 负载因子,这里取了0.75似乎java的工程师认为,100个盒子在用了75个的状态下,既能保证get的速度,将collision降的比较低,又不会浪费太多的盒子,文献上说,这个值大概在2/3左右即可。

    transient Entry[] table 这个值可以认为是一个队列,每一个entry后面都会跟着一个链表,最后hashmap逻辑样子便是:

transient int size key的数量。

 

下面开始说说hashmap的函数

static int hash(int h) {

        h ^= (h >>> 20) ^ (h >>> 12)

        return h ^ (h >>> 7) ^ (h >>> 4)

    }

    static int indexFor(int h, int length) {

        return h & (length-1) 由于length是2的倍数,减去1后全是1

    }

通过以上两个算法,算出了一个object在hash中的位置。有了以上的基础,看看hashmap的get和put到底是什么逻辑:

   public V get(Object key) {

        if (key == null)

            return getForNullKey();

        int hash = hash(key.hashCode());

        for (Entry<K,V> e = table[indexFor(hash, table.length)];

             e != null;

             e = e.next) {

            Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

                return e.value;

        }

        return null;

}

首先通过indexfor取到在这个table[]entry中的位置,然后开始按照这个链表循环依次往下找,

对于put:   

    public V put(K key, V value) {

        if (key == null)

            return putForNullKey(value);

        int hash = hash(key.hashCode());

        int i = indexFor(hash, table.length);

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {

            Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

 

        modCount++;

        addEntry(hash, key, value, i);

        return null;

}

这里存在两种情况:如果找到,则将原来的值替换,换成新的值;如果没有,则调用addEntry方法:

      void addEntry(int hash, K key, V value, int bucketIndex) {

    Entry<K,V> e = table[bucketIndex];

        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);

        if (size++ >= threshold)

            resize(2 * table.length);

}

这里首先用e找到在buket(木桶)table[]的位置,然后调用了在hashmap里的一个新的类构造函数enrty:  */

  Entry(int h, K k, V v, Entry<K,V> n) {

            value = v;

            next = n;

            key = k;

            hash = h;

        }

将新的值作为链表的首地址,并把n 作为后面的next保存起来,最后将引用丢给table[bucketIndex],这样变完成了put。

可是当put的值越来来越多,当size的大小超过threshold的时候(也就是认为100个位置放得东西太多了,有可能会indexfor散列到同一个位置,不方便get)便需要resize一下,这里变有可能造成一个死循环的问题,首先看看resize的函数:

    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);

        table = newTable;

        threshold = (int)(newCapacity * loadFactor);

}

这里调用最后一个函数transfer,于是,在并发的情况下,循环产生了:

void transfer(Entry[] newTable) {

        Entry[] src = table;

        int newCapacity = newTable.length;

        for (int j = 0; j < src.length; j++) {

            Entry<K,V> e = src[j];

            if (e != null) {

                src[j] = null;

                do {

                    Entry<K,V> next = e.next;

                    int i = indexFor(e.hash, newCapacity);

                    e.next = newTable[i];

                    newTable[i] = e;

                    e = next;

                } while (e != null);

            }

        }

    }

 

首先用src指向了原来的木桶数组,对其进行循环,如src[j]不为空,将它置为空,并对e进行循环(此处的e维持着一个经过IndexFor后算出的索引位置都在j上的一个链表)

在这里重新计算这个值得hash(因为长度变化了,所以新的位置有可能和原来的位置不一样,这也是为什么hashmap不能维持存储的信息的顺序的问题)然后将这个e的next指向了新的木桶位置,原来的木桶放得东西放在了这个e的后面。

比如在newTable[i]原来只有一个object a 现在有两个线程需要对src里的一个object b进行transfer,两个线程都算出要把它放在i的位置,在第一个线程已经完成的情况下(即现在newTable[i]现在的object是b-àa)第二个线程执行到e.next = newTable[i]

这里的e.next是b,它又指向了b,结果造成了b指向自己的问题,于是死循环诞生了,归根结底是因为

  e.next = newTable[i]

  newTable[i] = e 这两行代码的问题。

参考的资料http://en.wikipedia.org/wiki/Hash_table

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值