HashMap是线程不安全,在并发使用HashMap时会发生下列问题:
- 数据丢失
HashMap底层数据结构为数组,之后如果发送了哈希冲突,那么数据会以列表的形式保存在这个下标下,当数据长度大于8时,则会转为红黑树。
存在这样一个场景,同时进来两个元素,且保存下标也为同一个,此时下标下的末尾指向的元素就会是两个,就会有一个数据被覆盖掉,导致了数据丢失。
下面通过代码进行场景复现:
java">public static void test1() throws InterruptedException {HashMap<String, Integer> map = new HashMap<>();// 定义五个线程Thread[] threads = new Thread[5];// 往每个线程添加200条数据for (int i = 0; i<5; i++){int finalI = i;threads[i] = new Thread(() -> {for (int j = 0; j<200; j++){map.put(finalI + "_" + j, j);}});threads[i].start();}for (int i = 0; i<5; i++){threads[i].join();}// 最后输出Map中数据的数量System.out.println(map.size());}
输出结果(正确数量应该为1000):
java">905
- 数据结构转换异常
前面说过,若下标下列表过长,则会转化为红黑树进行扩容。那么就会存在这样一个场景,有两个线程,线程一往HashMap中的某个下标添加数据时,此时还没到达扩容临界点,还是以列表的结构去添加数据,线程二同时也往这个下标添加数据,但此时列表长度大于临界点了,发送了扩容,结构变成了红黑树,那么线程一还是以数组结构去添加数据,则会报错了。
下面通过代码进行场景复现:
java">public static void test2() throws InterruptedException {while (true){HashMap<String, Integer> map = new HashMap<>();// 定义五个线程Thread[] threads = new Thread[5];// 往每个线程添加200条数据for (int i = 0; i<5; i++){int finalI = i;threads[i] = new Thread(() -> {for (int j = 0; j<200; j++){map.put(finalI + "_" + j, j);}});threads[i].start();}for (int i = 0; i<5; i++){threads[i].join();}// 最后输出Map中数据的数量System.out.println(map.size());Thread.sleep(100);}}
输出结果:
java">Exception in thread "Thread-147800" Exception in thread "Thread-147803" java.lang.ClassCastException: java.util.HashMap$Node cannot be cast to java.util.HashMap$TreeNodeat java.util.HashMap$TreeNode.moveRootToFront(HashMap.java:1835)at java.util.HashMap$TreeNode.putTreeVal(HashMap.java:2014)at java.util.HashMap.putVal(HashMap.java:638)at java.util.HashMap.put(HashMap.java:612)at com.ooamo.test.HashMapTest.lambda$test2$1(HashMapTest.java:42)at java.lang.Thread.run(Thread.java:748)
java.lang.ClassCastException: java.util.HashMap$Node cannot be cast to java.util.HashMap$TreeNodeat java.util.HashMap$TreeNode.moveRootToFront(HashMap.java:1835)at java.util.HashMap$TreeNode.treeify(HashMap.java:1951)at java.util.HashMap.treeifyBin(HashMap.java:772)at java.util.HashMap.putVal(HashMap.java:644)at java.util.HashMap.put(HashMap.java:612)at com.ooamo.test.HashMapTest.lambda$test2$1(HashMapTest.java:42)at java.lang.Thread.run(Thread.java:748)
- 发生死列
这个问题是在HashMap1.7的时候才会发送,因为HashMap之前使用的是头插法进行数据插入,所以在并发执行的过程中可能会出现节点的指向发生了改动。
new->next = Head->next;
Head->next = new;