锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. ConcurrentHashMap 的优化及其与HashTable, HashMap的区别

ConcurrentHashMap 的优化及其与HashTable, HashMap的区别

0
  • 软件开发
  • 发布于 2024-09-19
  • 0 次阅读
黄健
黄健


目录

1.优化一:减小锁粒度

2.优化二:只针对写操作加锁

3.优化三:CAS

4.优化四:扩容方式



HashMap是线程不安全的,HashTable是线程安全的,关键方法加锁了.我们更推荐的是ConcurrentHashMap ,更优化的线程安全哈希表

接下来我们总结一下ConcurrentHashMap 进行了哪些优化,比HashTable好在哪里

1.优化一:减小锁粒度

最关键的优化:ConcurrentHashMap 相比于HashTable大大缩小了所冲突的概率,将一把大锁转换成多把小锁了

Hash Table做法是直接在方法上加synchronized,相当于给this加锁,只要操作哈希表上的任意元素,都会产生加锁,也就都可能发生锁冲突!

实际上不难发现,基于哈希表的结构特点,有些元素并发操作的时候,是不会产生线程安全问题的,也不需要锁控制!我们看看哈希表的结构

如果此时,元素12在同一个链表上,线程A修改元素1,线程B修改元素2,是否有线程安全问题呢??

修改(包括增删改),很明显是会有线程安全问题的,如果元素相邻,并发的删除/插入时,就需要修改两个节点的next的指向,这个情况是需要加锁的!再来看这种情况:

如果元素3和元素4 没有在相同的链表上,此时多线程并发操作34就不会有线程安全问题 ,也就相当于多个线程并发修改不同的变量,是没有线程安全问题的,也就不需要加锁

但是哈希表时直接加了个大锁

无论是12还是34这种情况,都是直接加锁,串行化了,那么就大大提高了锁冲突的概率,任何两个元素都会有锁冲突,即使是不在同一个链表上的,这也是不用哈希表的最主要原因!!

那么ConcurrentHashMap又是如何优化的呢?

ConcurrentHashMap的做法是:每个链表都有各自的锁,而不是整个哈希表只用一把锁了,具体来说,就是使用每个链表的头节点,作为锁对象.

两个线程针对同一个锁对象加锁才会产生锁竞争,才发生阻塞等待,针对不同的锁对象时不会有锁冲突的,所以就从哈希表的两个任意元素之间都有锁冲突转化为了只有同一链表上的任意元素之间才有锁冲突!!

此时是把锁的粒度变小了,针对12情况,是同一个链表上的元素,是针对相同的锁对象进行加锁,会有锁竞争,会保证线程安全.针对34情况,是针对不同的锁对象进行加锁,不会有锁竞争,没有阻塞等待,程序运行也会更快.

上述情况是jdk1.8及其以后的情况,1.7之前ConcurrentHashMap采用的是"分段锁"大概意思是让几个链表使用同一把锁.

分段锁,本质上也是缩小锁的范围,从而降低锁冲突的概率,但是这种做法不够合理,粒度切分的不够细,代码实现也更繁琐.

2.优化二:只针对写操作加锁

ConcurrentHashMap做了一个激进的优化操作,针对读操作,不加锁,只针对写操作加锁!

读和读之间没有冲突,写和写之间有冲突,读和写之间也没有冲突…其实很多场景下,读写之间不加锁控制,可能会读到写了一半的数据,相当于脏读了.写操作不是原子的(ConcurrentHashMap使用了volatile+原子的写操作维护线程安全)

3.优化三:CAS

ConcurrentHashMap内部充分使用了CAS,通过这个进一步的削减加锁操作的数目,比如维护元素个数..

4.优化四:扩容方式

针对扩容采取了"化整为零"的方式

Hash Map/HashTable扩容:

是创建一个更大的数组空间,把旧的数组上的链表上的每个元素搬运到新的数组上(删除加插入)

这个扩容操作会在某次put(插入后超过负载因子了)后触发,如果元素个数特别多,那么就会导致搬运操作太多,比较耗时,就会出现某次put比平常put卡很多倍,时间是用来搬运了

ConcurrentHashMap中的扩容是采取每次搬运一小部分元素.创建新的数组,旧数组也保留,每次put操作,都往新的数组添加,同时进行一部分搬运,把一小部分旧的元素搬运到新的数组上.

每次get的时候旧数组和新数组都查询,每次remove的时候,只是把元素删了就行,经过一段时间,所有的数据都搬运好了,最终再释放旧数组


原文链接: https://blog.csdn.net/chenchenchencl/article/details/128692598

标签: #软件开发 1171
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.