高并发下,hashmap会产生哪些问题?(代码片段)

lanqiu5ge lanqiu5ge     2022-12-29     234

关键词:

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为什么会死循环

作者| tech-bus.七十一来源| 程序员巴士前言  HashMap并发情况下产生的死循环问题在JDK1.7及之前版本是存在的,JDK1.8通过增加loHead头节点和loTail尾节点进行了修复,虽然进行了修复,但是如果涉及到并发情况下需要... 查看详情

高并发下如何避免产生重复数据?(代码片段)

前言最近测试给我提了一个bug,说我之前提供的一个批量复制商品的接口,产生了重复的商品数据。追查原因之后发现,这个事情没想象中简单,可以说一波多折。1.需求产品有个需求:用户选择一些品牌࿰... 查看详情

高并发下如何避免产生重复数据?(代码片段)

前言最近测试给我提了一个bug,说我之前提供的一个批量复制商品的接口,产生了重复的商品数据。追查原因之后发现,这个事情没想象中简单,可以说一波多折。1.需求产品有个需求:用户选择一些品牌࿰... 查看详情

hashmap在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么

Hashmap在并发环境下,可能出现的问题:1、多线程put时可能会导致get无限循环,具体表现为CPU使用率100%;原因:在向HashMapput元素时,会检查HashMap的容量是否足够,如果不足,则会新建一个比原来容量大两倍的Hash表,然后把数组... 查看详情

高并发下保证接口的幂等性的几种方式(代码片段)

高并发下保证接口的幂等性的几种方式​--洱涷Zz场景不知道你有没有遇到过这些场景:有时我们在填写某些form表单时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。我们在项目... 查看详情

高并发下保证接口的幂等性的几种方式(代码片段)

高并发下保证接口的幂等性的几种方式​--洱涷Zz场景不知道你有没有遇到过这些场景:有时我们在填写某些form表单时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。我们在项目... 查看详情

高并发下保证接口的幂等性的几种方式(代码片段)

高并发下保证接口的幂等性的几种方式​--洱涷Zz场景不知道你有没有遇到过这些场景:有时我们在填写某些form表单时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。我们在项目... 查看详情

高并发下减少锁竞争

1.减少锁的持有时间,将不需要锁的操作从同步代码块的移除。 //可以优化的代码  class AttributeStore{      private final Map<String,String> attributes=new HashMap& 查看详情

高并发下的web异步处理方案(代码片段)

高并发下的web异步处理方案一、问题介绍​平时web开发时(使用的servlet或者基于servlet封装的SpringMVC框架),业务处理基本都是同步处理,即业务处理与web容器接收线程为同一线程,每一次Http请求都由一个线... 查看详情

高并发下的内存管理技巧

  1、为何高并发下容易oom    1)首先我们了解当执行垃圾回收的时候,会导致进程暂停,从而使我们的程序卡死;进程长时间暂停,又会导致大量的请求积压等待处理,垃圾回收刚刚结束,更多的请求立刻涌进来,迅速... 查看详情

关于高并发下kafkaproducersend异步发送耗时问题的分析(代码片段)

最近开发网关服务的过程当中,需要用到kafka转发消息与保存日志,在进行压测的过程中由于是多线程并发操作kafkaproducer进行异步send,发现send耗时有时会达到几十毫秒的阻塞,很大程度上上影响了并发的性能,而在后续的测试... 查看详情

httpclient高并发下性能优化-http连接池(代码片段)

首先,明确三点:1.http连接池不是万能的,过多的长连接会占用服务器资源,导致其他服务受阻2.http连接池只适用于请求是经常访问同一主机(或同一个接口)的情况下3.并发数不高的情况下资源利用率低下那么,当你的业务符合上面3点,... 查看详情

高并发下秒杀商品,这9个细节得知道(代码片段)

大家好,我是bigsai,又跟大家见面了。前言高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。秒杀一般出... 查看详情

面试实战考核:设计一个高并发下的下单功能(代码片段)

功能需求:设计一个秒杀系统初始方案商品表设计:热销商品提供给用户秒杀,有初始库存。@EntitypublicclassSecKillGoodsimplementsSerializable@IdprivateStringid;/***剩余库存*/privateIntegerremainNum;/***秒杀商品名称*/privateStringgoodsName;秒杀订单表... 查看详情

高并发下秒杀商品,你必须知道的9个细节(代码片段)

前言高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。秒杀一般出现在商城的促销活动中,指定了一定数量&#... 查看详情

高并发下秒杀商品,你必须知道的9个细节(代码片段)

大家好,我是苏三,又跟大家见面了。前言高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。秒杀一般出... 查看详情

php和redis实现在高并发下的抢购及秒杀功能示例详解(代码片段)

...简单,但是有些问题需要解决,主要针对两个问题:一、高并发对数据库产生的压力二、竞争状态下如何解决库存的正确减少("超卖"问题)第一个问题,对于PHP来说很简单,用缓存技术就可以缓解数据库压力,比如memcache,redis... 查看详情

宜人贷系统架构——高并发下的进化之路

宜人贷系统版本的迭代1.0版本——简单的烦恼1.PNG迭代之前宜人贷的系统,其实就是一个前台,一个后台,一个DB,前台采用的是多机部署的方式。软件层也是跟最传统的软件一样分三层,第一层是Controller,第二层是Service... 查看详情