首页 > 编程语言 >[Java并发]ConcurrentHashMap

[Java并发]ConcurrentHashMap

时间:2024-06-23 16:55:09浏览次数:3  
标签:ConcurrentHashMap Java 插入 next 链表 并发 线程 hash

ConcurrentHashMap

HashMap和ConcurrentHashMap的区别

主要区别就是hashmap线程不安全,ConcurrentHashMap线程安全

HashMap线程不安全,有以下两个问题

put覆盖问题

比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的桶索引和线程B要插入的记录计算出来的桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。

死循环问题

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    // 遍历数组,仅遍历数组下标元素
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            // 只有产生了新的hash表才需要重新计算hash值
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            // 这里读者可以验证:i = oldIndex 或 i = oldIndex + oldCapacity
            int i = indexFor(e.hash, newCapacity);
            // 链表前插法
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

死循环问题是指如果两个线程同时对hashmap进行扩容,因为hashmap进行元素复制的方法是头插法,会产生next指针成环的问题,导致后面的数据无法访问到。

这个视频讲得很清楚

https://www.bilibili.com/video/BV1z54y1i73r/?spm_id_from=333.999.0.0&vd_source=2254c66bb775d7bf8d83535888768545

HashTable的效率又太低

因为hashtable的方法用synchronized修饰,相当于synchronized(this),导致只能有一个线程访问hashtable,效率太低

ConcurrentHashMap原理

在JDK7中原理,采用数组+Segment的段锁的数据结构,其中Segment继承ReentrantLock

在JDK8中,采用数组+链表+红黑树的数据结构,采用CAS+synchronized保证线程安全

JDK7对Segment进行加锁,JDK8对数组中每个元素(Node)加锁

查询时间复杂度 遍历链表O(N), 红黑树(O(logN))

具体原理请参考下文

https://blog.csdn.net/cai519678181/article/details/105165853/?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-4--blog-89047318.235v43pc_blog_bottom_relevance_base4&spm=1001.2101.3001.4242.3&utm_relevant_index=7

https://www.cnblogs.com/jxxblogs/p/12517197.html

标签:ConcurrentHashMap,Java,插入,next,链表,并发,线程,hash
From: https://www.cnblogs.com/DCFV/p/18263603

相关文章

  • [Java基础]String
    String常量池/运行时常量池java类编译之后生成的.class文件包含三部分信息,类的基本信息,常量池,方法的定义通过javap-vxxxx.class命令可以看到Constantpool:#1=Methodref#2.#3//java/lang/Object."<init>":()V#2=Class#4......
  • Java Stream 8 API
    动态多字段排序动态多字段排序假设我们有一个Person类,希望能够按照age和name进行动态排序。我们使用上述代码生成一个组合比较器来完成多字段排序。1.定义Person类java复制代码importjava.util.HashMap;importjava.util.Map;publicclassPerson{privateM......
  • [Golang并发]GMP模型
    什么是GoroutineGoroutine=Golang+Coroutine。Goroutine是golang实现的协程,是用户级线程。Goroutine的特点:相比线程,其启动的代价很小,以很小栈空间启动(2Kb左右)能够动态地伸缩栈的大小,最大可以支持到Gb级别工作在用户态,切换成很小与线程关系是n:m,即可以在n个系统线程上多......
  • vscode开发纯java项目兼容eclipse
    最近想使用vscode作为开发工具逐步替代eclipse,但是不影响eclipse作为项目管理的配置。以下是踩坑过程:1、项目之间的依赖。如主projectA依赖projectB,projectB并不是已jar包的形式,而是项目的形式在eclipse中的,eclipse有个很方便的功能是直接把项目添加进依赖中,vscode貌似找不到直接......
  • Java基础面试题下
    #Java基础面试题(下)>lecture:波哥#一、String相关面试题##1.为什么String在java中是不可变的?-如果不是不可变的:这种情况根本不可能,因为在字符串池的情况下,一个字符串对象/文字,例如“Test”已被许多参考变量引用,因此如果其中任何一个更改了值,其他参数将自动受到影......
  • 掌握Perl并发:线程与进程编程全攻略
    掌握Perl并发:线程与进程编程全攻略引言Perl作为一种功能强大的编程语言,提供了丰富的并发编程手段。无论是通过threads模块实现的线程,还是通过fork系统调用产生的进程,Perl都能帮助开发者高效地处理多任务。本文将深入探讨如何在Perl中使用线程和进程,带领读者掌握并发编程的......
  • 数据库系统概论(超详解!!!) 第十四节 数据库并发控制机制
    多用户数据库系统:允许多个用户同时使用的数据库系统例:飞机定票数据库系统银行数据库系统特点:在同一时刻并发运行的事务数可达数百上千个多事务执行方式:(1)事务串行执行每个时刻只有一个事务运行,其他事务必须等到这个事务结束以后方能运行。不能充分利用系统资源,发挥数据库......
  • 1.4Java 基本数据类型
    变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。因此,通过定义不同类型的变量,可以在内存中储存整数、小数或者字符。Java的两大数据类型:内置数据类型引用......
  • 【JavaScript脚本宇宙】编写可靠代码:探索最佳JavaScript类型检查解决方案
    掌握类型安全:选择适合您的JavaScript类型检查工具前言JavaScript作为一种动态类型语言,在大型项目的开发中常常会遇到类型错误和难以调试的问题。为了解决这些问题,出现了各种类型的JavaScript类型检查工具。这些工具能够帮助开发人员在代码编写过程中及时发现潜在的类型错......
  • 探索Java正则表达式的奥秘:源码之旅与高级应用
    1.引言在Java编程中,正则表达式(RegularExpression,简称Regex)是一个强大的工具,用于处理字符串匹配、查找和替换等任务。Java提供了java.util.regex包来支持正则表达式的功能。对于Java工程师来说,理解其背后的工作原理和源码实现,可以进一步掌握其性能特性和最佳实践。2.ja......