iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >ConcurrentHashMap是如何保证线程安全
  • 641
分享到

ConcurrentHashMap是如何保证线程安全

2024-04-02 19:04:59 641人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录jdk 1.7 底层实现JDK 1.7 线程安全实现JDK 1.8 底层实现JDK 1.8 线程安全实现总结ConcurrentHashMap 是 HashMap 的多线程版本,

ConcurrentHashMap 是 HashMap 的多线程版本,HashMap 在并发操作时会有各种问题,比如死循环问题、数据覆盖等问题。而这些问题,只要使用 ConcurrentHashMap 就可以完美解决了,那问题来了,ConcurrentHashMap 是如何保证线程安全的?它的底层又是如何实现的?接下来我们一起来看。

JDK 1.7 底层实现

ConcurrentHashMap 在不同的 JDK 版本中实现是不同的,在 JDK 1.7 中它使用的是数组链表的形式实现的,而数组又分为:大数组 Segment 和小数组 HashEntry。 大数组 Segment 可以理解为 Mysql 中的数据库,而每个数据库(Segment)中又有很多张表 HashEntry,每个 HashEntry 中又有多条数据,这些数据是用链表连接的,如下图所示:

为什么ConcurrentHashMap是线程安全的?_数组

JDK 1.7 线程安全实现

了解了 ConcurrentHashMap 的底层实现,再看它的线程安全实现就比较简单了。
接下来,我们通过添加元素 put 方法,来看 JDK 1.7 中 ConcurrentHashMap 是如何保证线程安全的,具体实现源码如下:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    // 在往该 Segment 写入前,先确保获取到
    HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value); 
    V oldValue;
    try {
        // Segment 内部数组
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                K k;
                // 更新已有值...
            }
            else {
                // 放置 HashEntry 到特定位置,如果超过阈值则进行 rehash
                // 忽略其他代码...
            }
        }
    } finally {
        // 释放锁
        unlock();
    }
    return oldValue;
}

从上述源码我们可以看出,Segment 本身是基于 ReentrantLock 实现的加锁和释放锁的操作,这样就能保证多个线程同时访问 ConcurrentHashMap 时,同一时间只有一个线程能操作相应的节点,这样就保证了 ConcurrentHashMap 的线程安全了。
也就是说 ConcurrentHashMap 的线程安全是建立在 Segment 加锁的基础上的,所以我们把它称之为分段锁或片段锁,如下图所示:

为什么ConcurrentHashMap是线程安全的?_数组_02

JDK 1.8 底层实现

在 JDK 1.7 中,ConcurrentHashMap 虽然是线程安全的,但因为它的底层实现是数组

DK 1.7 中,ConcurrentHashMap 虽然是线程安全的,但因为它的底层实现是数组 + 链表的形式,所以在数据比较多的情况下访问是很慢的,因为要遍历整个链表,而 JDK 1.8 则使用了数组 + 链表/红黑树的方式优化了 ConcurrentHashMap 的实现,具体实现结构如下:

为什么ConcurrentHashMap是线程安全的?_链表_03

链表升级为红黑树的规则:当链表长度大于 8,并且数组的长度大于 64 时,链表就会升级为红黑树的结构。

PS:ConcurrentHashMap 在 JDK 1.8 虽然保留了 Segment 的定义,但这仅仅是为了保证序列化时的兼容性,不再有任何结构上的用处了。

JDK 1.8 线程安全实现

在 JDK 1.8 中 ConcurrentHashMap 使用的是 CAS

DK 1.8 中 ConcurrentHashMap 使用的是 CAS + volatile 或 synchronized 的方式来保证线程安全的,它的核心实现源码如下:

final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 节点为空
            // 利用 CAS 去进行无锁线程安全操作,如果 bin 是空的
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break; 
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else if (onlyIfAbsent
                 && fh == hash
                 && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                 && (fv = f.val) != null)
            return fv;
        else {
            V oldVal = null;
            synchronized (f) {
                   // 细粒度的同步修改操作... 
                }
            }
            // 如果超过阈值,升级为红黑树
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

从上述源码可以看出,在 JDK 1.8 中,添加元素时首先会判断容器是否为空,如果为空则使用 volatile 加 CAS 来初始化。如果容器不为空则根据存储的元素计算该位置是否为空,如果为空则利用 CAS 设置该节点;如果不为空则使用 synchronize 加锁,遍历桶中的数据,替换或新增节点到桶中,最后再判断是否需要转为红黑树,这样就能保证并发访问时的线程安全了。
我们把上述流程简化一下,我们可以简单的认为在 JDK 1.8 中,ConcurrentHashMap 是在头节点加锁来保证线程安全的,锁的粒度相比 Segment 来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提高了。而且 JDK 1.8 使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的 O(n) 优化到了 O(logn) 的时间复杂度,具体加锁示意图如下:

为什么ConcurrentHashMap是线程安全的?_线程安全_04

总结

ConcurrentHashMap 在 JDK 1.7 时使用的是数据加链表的形式实现的,其中数组分为两类:大数组 Segment 和小数组 HashEntry,而加锁是通过给 Segment 添加 ReentrantLock 锁来实现线程安全的。而 JDK 1.8 中 ConcurrentHashMap 使用的是数组+链表/红黑树的方式实现的,它是通过 CAS 或 synchronized 来实现线程安全的,并且它的锁粒度更小,查询性能也更高。

到此这篇关于ConcurrentHashMap是如何保证线程安全的文章就介绍到这了,更多相关ConcurrentHashMap线程安全内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: ConcurrentHashMap是如何保证线程安全

本文链接: https://www.lsjlt.com/news/163516.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • ConcurrentHashMap是如何保证线程安全
    目录JDK 1.7 底层实现JDK 1.7 线程安全实现JDK 1.8 底层实现JDK 1.8 线程安全实现总结ConcurrentHashMap 是 HashMap 的多线程版本,...
    99+
    2024-04-02
  • 详解CopyOnWriteArrayList是如何保证线程安全
    目录一:前言二:成员变量分析三:源码分析1.空参构造2.传入一个Collection对象的构造方法3.传入一个数组的构造方法四:总结一:前言 在我们需要保证线程安全的时候,如果使用到...
    99+
    2024-04-02
  • Java中ConcurrentHashMap是如何实现线程安全
    目录语法: ConcurrentHashmap 的需要:如何使 ConcurrentHashMap 线程安全成为可能?Hashtable、Hashmap、ConcurrentHash...
    99+
    2024-04-02
  • 浅谈springboot如何保证多线程安全
    目录如何保证多线程安全1.springboot在多线程并发访问下是怎么做的2.controller在多线程下如何尽可能保证线程安全,如何取舍3.小结一下单例模式与线程安全问题踩的坑下...
    99+
    2024-04-02
  • java多线程怎么保证线程安全
    Java中有多种方式可以保证线程安全,以下是一些常见的方法:1. 使用synchronized关键字:使用synchronized关...
    99+
    2023-09-13
    java
  • ConcurrentHashMap是如何实现线程安全的你知道吗
    目录1. 前言2. ConcurrentHashMap 是如何实现线程安全的2.1. 初始化数据结构时的线程安全2.2. put 操作时的线程安全2.3. get 操作时的线程安全2...
    99+
    2022-11-13
    ConcurrentHashMap 线程安全 ConcurrentHashMap线程安全
  • springboot怎么保证多线程安全
    小编给大家分享一下springboot怎么保证多线程安全,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!如何保证多线程安全1.springboot在多线程并发访问下是怎么做的我们在Controller下,一般都是@AutoW...
    99+
    2023-06-22
  • 高并发怎么保证线程安全
    线程安全有原子性、可见性、有序性这3个方面,我们可以从这三个方面入手,可以通过加锁来保证线程安全,如果对象是immutable,则是线程安全,可放心使用,也可以使用synchronized关键字来保证线程安全。...
    99+
    2024-04-02
  • Java对象的内存分配过程是如何保证线程安全的
    本篇内容介绍了“Java对象的内存分配过程是如何保证线程安全的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在开始进入正题之前,请允许我问一...
    99+
    2023-06-16
  • Java中ConcurrentHashMap是怎么实现线程安全的
    这篇文章主要介绍“Java中ConcurrentHashMap是怎么实现线程安全的”,在日常操作中,相信很多人在Java中ConcurrentHashMap是怎么实现线程安全的问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希...
    99+
    2023-06-25
  • 如何保证小程序的安全性
    要保证小程序的安全性,可以采取以下措施: 使用合法的开发工具和平台:确保使用官方或可信赖的开发工具和平台进行小程序开发,避免使用...
    99+
    2024-04-17
    小程序
  • Go语言atomic.Value如何不加锁保证数据线程安全?
    目录引言atomic.Value的使用方式atomic.Value的内部实现写入线程安全的保证读取(Load)操作总结引言 很多人可能没有注意过,在 Go(甚至是大部分语言)中,一条...
    99+
    2023-05-20
    Go语言atomic.Value Go数据线程安全
  • 分布式下redis怎么保证线程安全
    在分布式环境下,Redis本身是单线程模型的,因此不需要额外的线程安全措施。然而,在使用Redis的客户端与服务器进行通信时,可能需...
    99+
    2023-10-25
    redis
  • JVM 是怎么设计来保证new对象的线程安全
    1、采用 CAS 分配重试的方式来保证更新操作的原子性 2、每个线程在 Java 堆中预先分配一小块内存,也就是本地线程分配缓冲(Thread Local AllocationBuffer,TLAB),要分配内存的线程,先在本地缓冲区中分配...
    99+
    2023-08-30
    jvm
  • 详解SpringBoot是如何保证接口安全的
    目录为什么要保证接口安全防篡改如何解决防重放基于timestamp的方案基于nonce + timestamp 的方案代码实现1、构建请求头对象2、工具类从HttpServletRe...
    99+
    2023-02-02
    SpringBoot保证接口安全 SpringBoot 接口安全
  • Java中使用ConcurrentHashMap实现线程安全的Map
    目录jdk1.7版本数据结构get()put()size()扩容jdk1.8版本数据结构put()扩容jdk1.7版本 和其他并发集合的区别 待完善 数据结构 fi...
    99+
    2023-05-17
    Java ConcurrentHashMap Java线程安全
  • Win XP远程控制时如何保证安全
    跟其他远程控制技术类似,远程协助和远程桌面同样要在使用前考虑好安全问题。对于最高级别的安全要求,根本不建议在实际应用中使用远程控制技术,不过要明白这种技术也能给用户带来便利。本章会就使用远程控制技术时保证安全性的方法进行...
    99+
    2023-05-24
    Win XP远程控制 保证安全 XP
  • Java对象的内存分配是怎么保证线程安全的
    本篇内容主要讲解“Java对象的内存分配是怎么保证线程安全的”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java对象的内存分配是怎么保证线程安全的”吧!JVM内存结构,是很重要的知识,相信每一...
    99+
    2023-06-16
  • HTTPS是怎么保证安全的
    本篇内容介绍了“HTTPS是怎么保证安全的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、安全特性在什么是HTTP这篇文章中,我们了解到H...
    99+
    2023-06-17
  • 在java中利用volatile课余保证线程安全吗
    本篇文章为大家展示了在java中利用volatile课余保证线程安全吗,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。java的volatile关键字到底能不能保证线程安全,经过实践,volatile...
    99+
    2023-05-31
    java volatile ava
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作