面试必问之 ConcurrentHashMap 线程安全的具体实现方式(上)

简介: ConcurrentHashMap 是 Java 并发包中提供的一个线程安全且高效的 HashMap 实现,以弥补 HashMap 不适合在并发环境中操作使用的不足,本文就来分析下 ConcurrentHashMap 的实现原理,并对其实现原理进行分析!

一、摘要

在之前的集合文章中,我们了解到 HashMap 在多线程环境下操作可能会导致程序死循环的线上故障!

既然在多线程环境下不能使用 HashMap,那如果我们想在多线程环境下操作 map,该怎么操作呢?

想必阅读过小编之前写的《HashMap 在多线程环境下操作可能会导致程序死循环》一文的朋友们一定知道,其中有一个解决办法就是使用 java 并发包下的 ConcurrentHashMap 类!

今天呢,我们就一起来聊聊 ConcurrentHashMap 这个类!

二、简介

众所周知,在 Java 中,HashMap 是非线程安全的,如果想在多线程下安全的操作 map,主要有以下解决方法:

  • 第一种方法,使用Hashtable线程安全类;
  • 第二种方法,使用Collections.synchronizedMap方法,对方法进行加同步锁;
  • 第三种方法,使用并发包中的ConcurrentHashMap类;

在之前的文章中,关于 Hashtable 类,我们也有所介绍,Hashtable 是一个线程安全的类,Hashtable 几乎所有的添加、删除、查询方法都加了synchronized同步锁!

相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞等待需要的锁被释放,在竞争激烈的多线程场景中性能就会非常差,所以 Hashtable 不推荐使用!

111.jpg

再来看看第二种方法,使用Collections.synchronizedMap方法,我们打开 JDK 源码,部分内容如下:

112.jpg

可以很清晰的看到,如果传入的是 HashMap 对象,其实也是对 HashMap 做的方法做了一层包装,里面使用对象锁来保证多线程场景下,操作安全,本质也是对 HashMap 进行全表锁!

使用Collections.synchronizedMap方法,在竞争激烈的多线程环境下性能依然也非常差,所以不推荐使用!

上面 2 种方法,由于都是对方法进行全表锁,所以在多线程环境下容易造成性能差的问题,因为** hashMap 是数组 + 链表的数据结构,如果我们把数组进行分割多段,对每一段分别设计一把同步锁,这样在多线程访问不同段的数据时,就不会存在锁竞争了,这样是不是可以有效的提高性能?**

再来看看第三种方法,使用并发包中的ConcurrentHashMap类!

ConcurrentHashMap 类所采用的正是分段锁的思想,将 HashMap 进行切割,把 HashMap 中的哈希数组切分成小数组,每个小数组有 n 个 HashEntry 组成,其中小数组继承自ReentrantLock(可重入锁),这个小数组名叫Segment 如下图:113.jpg

当然,JDK1.7 和 JDK1.8 对 ConcurrentHashMap 的实现有很大的不同!

JDK1.8 对 HashMap 做了改造,当冲突链表长度大于 8 时,会将链表转变成红黑树结构,上图是 ConcurrentHashMap 的整体结构,参考 JDK1.7!

我们再来看看 JDK1.8 中 ConcurrentHashMap 的整体结构,内容如下:

114.jpg

JDK1.8 中 ConcurrentHashMap 类取消了 Segment 分段锁,采用 CAS + synchronized 来保证并发安全,数据结构跟 jdk1.8 中 HashMap 结构类似,都是数组 + 链表(当链表长度大于 8 时,链表结构转为红黑二叉树)结构。

ConcurrentHashMap 中 synchronized 只锁定当前链表或红黑二叉树的首节点,只要节点 hash 不冲突,就不会产生并发,相比 JDK1.7 的 ConcurrentHashMap 效率又提升了 N 倍!

说了这么多,我们再一起来看看 ConcurrentHashMap 的源码实现。

三、JDK1.7 中的 ConcurrentHashMap

JDK 1.7 的 ConcurrentHashMap 采用了非常精妙的分段锁策略,打开源码,可以看到 ConcurrentHashMap 的主存是一个 Segment 数组。

115.jpg

我们再来看看 Segment 这个类,在 ConcurrentHashMap 中它是一个静态内部类,内部结构跟 HashMap 差不多,源码如下:116.jpg

存放元素的 HashEntry,也是一个静态内部类,源码如下:

117.jpgHashEntryHashMap中的 Entry非常类似,唯一的区别就是其中的核心数据如value ,以及next都使用了volatile关键字修饰,保证了多线程环境下数据获取时的可见性

从类的定义上可以看到,Segment 这个静态内部类继承了ReentrantLock类,ReentrantLock是一个可重入锁,如果了解过多线程的朋友们,对它一定不陌生。

ReentrantLocksynchronized都可以实现对线程进行加锁,不同点是:ReentrantLock可以指定锁是公平锁还是非公平锁,操作上也更加灵活,关于此类,具体在以后的多线程篇幅中会单独介绍。

因为ConcurrentHashMap的大体存储结构和HashMap类似,所以就不对每个方法进行单独分析介绍了,关于HashMap的分析,有兴趣的朋友可以参阅小编之前写的《深入分析 HashMap》一文。

ConcurrentHashMap 在存储方面是一个 Segment 数组,一个 Segment 就是一个子哈希表,Segment 里维护了一个 HashEntry 数组,其中 Segment 继承自 ReentrantLock,并发环境下,对于不同的 Segment 数据进行操作是不用考虑锁竞争的,因此不会像 Hashtable 那样不管是添加、删除、查询操作都需要同步处理。

理论上 ConcurrentHashMap 支持 concurrentLevel(通过 Segment 数组长度计算得来) 个线程并发操作,每当一个线程独占一把锁访问 Segment 时,不会影响到其他的 Segment 操作,效率大大提升!

相关文章
|
2月前
|
安全
ConcurrentHashMap原如何保证的线程安全?
JDK1.7:使用分段锁,将一个Map分为了16个段,每个段都是一个小的hashmap,每次操作只对其中一个段加锁 JDK1.8:采用CAS+Synchronized保证线程安全,每次插入数据时判断在当前数组下标是否是第一次插入,是就通过CAS方式插入,然后判断f.hash是否=-1,是的话就说明其他线程正在进行扩容,当前线程也会参与扩容;删除方法用了synchronized修饰,保证并发下移除元素安全
77 2
|
15天前
|
并行计算 算法 安全
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
68 3
|
2月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
2月前
|
存储 缓存 安全
大厂面试高频:ConcurrentHashMap 的实现原理( 超详细 )
本文详细解析ConcurrentHashMap的实现原理,大厂高频面试,必知必备。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:ConcurrentHashMap 的实现原理( 超详细 )
|
14天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
14天前
|
Java 调度
|
4月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
|
4月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
87 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
4月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
63 2
面试官:说说停止线程池的执行流程?
|
4月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
46 1
面试官:如何实现线程池任务编排?
下一篇
开通oss服务