HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。数组
咱们来分析一下多线程访问:多线程
1.在hashmap作put操做的时候会调用下面方法:this
// 新增Entry。将“key-value”插入指定位置,bucketIndex是位置索引。 void addEntry(int hash, K key, V value, int bucketIndex) { // 保存“bucketIndex”位置的值到“e”中 Entry<K,V> e = table[bucketIndex]; // 设置“bucketIndex”位置的元素为“新Entry”, // 设置“e”为“新Entry的下一个节点” table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小 if (size++ >= threshold) resize(2 * table.length); }
在hashmap作put操做的时候会调用到以上的方法。如今假如A线程和B线程同时对同一个数组位置调用addEntry,两个线程会同时获得如今的头结点,而后A写入新的头结点以后,B也写入新的头结点,那B的写入操做就会覆盖A的写入操做形成A的写入操做丢失spa
2.删除键值对的代码线程
<span style="font-size: 18px;"> </span>// 删除“键为key”的元素 final Entry<K,V> removeEntryForKey(Object key) { // 获取哈希值。若key为null,则哈希值为0;不然调用hash()进行计算 int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; // 删除链表中“键为key”的元素 // 本质是“删除单向链表中的节点” while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
当多个线程同时操做同一个数组位置的时候,也都会先取得如今状态下该位置存储的头结点,而后各自去进行计算操做,以后再把结果写会到该数组位置去,其实写回的时候可能其余的线程已经就把这个位置给修改过了,就会覆盖其余线程的修改。code
3.addEntry中当加入新的键值对后键值对总数量超过门限值的时候会调用一个resize操做,代码以下:blog
// 从新调整HashMap的大小,newCapacity是调整后的容量 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; //若是就容量已经达到了最大值,则不能再扩容,直接返回 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 新建一个HashMap,将“旧HashMap”的所有元素添加到“新HashMap”中, // 而后,将“新HashMap”赋值给“旧HashMap”。 Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }
这个操做会新生成一个新的容量的数组,而后对原数组的全部键值对从新进行计算和写入新的数组,以后指向新生成的数组。索引
当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操做,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其余线程的均会丢失。并且当某些线程已经完成赋值而其余线程刚开始的时候,就会用已经被赋值的table做为原始数组,这样也会有问题。ci