HashMap在高并发环境下会产生的问题

HashMap其实并不是线程安全的,在高并发的情况下,会产生并发引起的问题:

比如:

  • HashMap死循环,造成CPU100%负载
  • 触发fail-fast

下面逐个分析下出现上述情况的原因:

HashMap死循环的原因

HashMap进行存储时,如果size超过(当前最大容量*负载因子)时候会发生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方法是真正执行rehash的操作,容易在高并发时发生问题
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);
}
}
}

HashMap死循环演示:

假如有两个线程P1、P2,以及table[]某个节点链表为 a->b->null(a、b是HashMap的Entry节点,保存着Key-Value键值对的值)

  1. P1先执行,执行完"Entry<K,V> next = e.next;"代码后,P1发生阻塞或者其他情况不再执行下去,此时e=a,next=b

  2. P1阻塞后P2获得CPU资源开始执行,由于P1并没有执行完transfer(),table 和 threshold仍为原来的值,P2依旧会进行resize操作,并且P2顺利执行完resize()方法,假设a、b节点仍然rehash到newTable[](注意,P1和P2中newTable[]不是同一个)中同一个节点链表中,则新的节点链表为 b->a->null

        transfer(newTable);   //P1阻塞在transfer方法中,没有执行到下边对 table 和 threshold 重新赋值的操作
    table = newTable;
    threshold = (int)(newCapacity * loadFactor);
  3. P1又继续执行"Entry<K,V> next = e.next;"之后的代码,则newTable[i]的节点链表变化过程为:

    • 第一次while循环,newTable[i]=a,链表为:b->a->null;此时e=b;
    • 进入第二次循环,newTable[i]=b,链表为:b->a->a; 此时a<->a出现回路,e=a, while(e!=null)死循环

触发fail-fast

一个线程利用迭代器迭代时,另一个线程做插入删除操作,造成迭代的fast-fail。

public class TestFailFast {

    private static final String USER_NAME_PREFIX = "User-";
// Key: User Name, Value: User Age
private static Map<String, Integer> userMap = new HashMap<>(); // ThreadA 用于向HashMap添加元素
static class ThreadA implements Runnable {
@Override
public void run() {
System.out.println("ThreadA starts to add user.");
for (int i = 1; i < 100000; i++) {
userMap.put(USER_NAME_PREFIX+i, i%100);
}
System.out.println("ThreadA done.");
}
} // ThreadB 用于遍历HashMap中元素输出
static class ThreadB implements Runnable {
@Override
public void run() {
System.out.println("ThreadB starts to iterate.");
for (Map.Entry<String, Integer> user : userMap.entrySet()) {
System.out.println("UserName=" + user.getKey()
+ ", UserAge=" + user.getValue());
}
System.out.println("ThreadB done.");
}
} public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new ThreadA());
Thread threadB = new Thread(new ThreadB()); threadA.start();
threadB.start(); threadA.join();
threadB.join();
System.exit(0);
}
}

运行结果:抛出ConcurrentModificationException

ThreadA starts to add user.
ThreadB starts to iterate.
Exception in thread "Thread-1" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
at concurrent.TestFailFast$ThreadB.run(TestFailFast.java:33)
at java.lang.Thread.run(Thread.java:748)
ThreadA done.

总结

HashMap并非线程安全,所以在多线程情况下,应该首先考虑用ConcurrentHashMap,避免悲剧的发生。

参考资料:

https://blog.csdn.net/chenxuegui1234/article/details/39646041

https://blog.csdn.net/u011716215/article/details/78601916

高并发下,HashMap会产生哪些问题?的更多相关文章

  1. JDK1.7 ConcurrentHashMap--解决高并发下的HashMap使用问题

    高并发下也可以使用HashTable .Collections.synchronizedMap因为他们是线程安全的,但是却牺牲了性能,无论是读操作.写操作都是给整个集合加锁,导致同一时间内其他操作均为 ...

  2. JDK1.7 高并发下的HashMap

    HashMap的容量是有限的.当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高. 这时候,HashMap需要扩展它的长度,也就是进行Resize. 影响发 ...

  3. 对HashMap的理解(二):高并发下的HashMap

    在分析hashmap高并发场景之前,我们要先搞清楚ReHash这个概念.ReHash是HashMap在扩容时的一个步骤.HashMap的容量是有限的.当经过多次元素插入,使得HashMap达到一定饱和 ...

  4. HashMap高并发下存在的问题

    原文链接:https://blog.csdn.net/bjwfm2011/article/details/81076736 1.什么是HashMap? HashMap底层原理 HashMap是存储键值 ...

  5. 高并发下的Java数据结构(List、Set、Map、Queue)

    由于并行程序与串行程序的不同特点,适用于串行程序的一些数据结构可能无法直接在并发环境下正常工作,这是因为这些数据结构不是线程安全的.本节将着重介绍一些可以用于多线程环境的数据结构,如并发List.并发 ...

  6. Java高并发下多线程编程

    1.创建线程 Java中创建线程主要有三种方式: 继承Thread类创建线程类: 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此也把run方法称为 ...

  7. php结合redis实现高并发下的抢购、秒杀功能

    抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖"问题)对于第一个问题,已经很容易想到用缓存 ...

  8. 高并发下MySQL出现checking permissions

    在某些数据访问层框架中,会使用show full tables from test like 'demo',来检查数据库的状态.当数据库中表的数量较少时,并没有出现严重的问题.但是当数据库中的表数量多 ...

  9. EF+MySQL乐观锁控制电商并发下单扣减库存,在高并发下的问题

    下订单减库存的方式 现在,连农村的大姐都会用手机上淘宝购物了,相信电商对大家已经非常熟悉了,如果熟悉电商开发的同学,就知道在买家下单购买商品的时候,是需要扣减库存的,当然有2种扣减库存的方式, 一种是 ...

  10. (高级篇)php结合redis实现高并发下的抢购、秒杀功能

    抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖"问题)对于第一个问题,已经很容易想到用缓存 ...

随机推荐

  1. Scrum----学习心得

    Scrum学习心得 什么是敏捷开发? 敏捷开发(Agile Development)是一种以人为核心.迭代.循序渐进的 开发方法.它不是一门技术,它是一种开发方法,也就是一种软件开发的流程,它会指导我 ...

  2. 如何为Android平台编译 opencv3 和 opencv_contrib (Linux)

    编译出来的opencv库有问题,正在调试中 ...... 本文以编译opencv 3.3.0 和 opencv_contrib 3.3.0为例,系统为 Linux x64 (Fedora 21),具体 ...

  3. tomcat + jdk 快速部署

    环境: apache-tomcat-7.0.73 java version "1.8.0_112" 注释:创建普通用户,使用 sudu进行操作,本文略写. 1.JDK 1.jdk配 ...

  4. Less的guards and argument matching

    less guards/argument matching: .setbackground(@number) when (@number>0){ .setbackground( @number ...

  5. mongodb使用mongos链接复制集

    我们知道mongodb现在生产环境大部分还是使用的复制集来作为主流,据我了解.很多公司现在都是使用复制集的方式,而我们通过mongos可以巧妙的解决链接的问题,我们先从环境说起: 首先我们要模仿搭建的 ...

  6. 一句DOS命令搞定文件合并

    用Dos的copy命令实现: copy a.js+b.js+c.js abc.js /b 将 a.js b.js c.js 合并为一个 abc.js,最后的 /b 表示文件为二进位文件,copy 命令 ...

  7. Eigen参考资料

    Getting started https://eigen.tuxfamily.org/dox/GettingStarted.html long tutorial https://eigen.tuxf ...

  8. SecurityError: The operation is insecure.(js不安全操作)

    今天突然就遇上了这样的情况,本来在出错的这一行的后面,还有要执行的语句,都没有办法执行,真实坑爹,而最要命的事情,这样的情况,在我的chrome浏览器里没有,但是在firefox里就会出现. The ...

  9. mongodb常用基础命令

    查看当前使用的数据库,也可以直接用dbdb.getName() 显示当前db状态db.stats() 当前db版本 db.version() 查看当前db的链接机器地址db.getMongo() 查看 ...

  10. luogu P3941 入阵曲

    嘟嘟嘟 这道题我觉得跟最大子矩阵那道题非常像,都是O(n4)二维前缀和暴力很好想,O(n3)正解需要点转化. O(n4)暴力就不说啦,二维前缀和,枚举所有矩形,应该能得55分. O(n3)需要用到降维 ...