【Java集合篇】HashMap、Hashtable 和 ConcurrentHashMap的区别

news2024/10/6 10:42:18

在这里插入图片描述


HashMap、Hashtable和ConcurrentHashMap的区别

  • ✔️ 三者区别
    • ✔️ 线程安全方面
    • ✔️继承关系方面
    • ✔️ 允不允许null值方面
      • ✔️为什么ConcurrentHashMap不允许null值?
    • ✔️ 默认初始容量和扩容机制
    • ✔️遍历方式的内部实现上不同


✔️ 三者区别


✔️ 线程安全方面


HashMap是非线程安全的。


Hashtable 中的方法是同步的,所以它是线程安全的。


ConcurrentHashMap 在JDK 1.8之前使用分段锁保证线程安全,ConcurrentHashMap默认情况下将 hash 表分为16个桶(分片),在加锁的时候,针对每个单独的分片进行加锁,其他分片不受影响。锁的粒度更细,所以他的性能更好。


ConcurrentHashMap 在JDK 1.8中,采用了一种新的方式来实现线程安全,即使用了CAS+synchronized ,这个实现被称为"分段锁"的变种,也被称为"锁分离”,它将锁定粒度更细,把锁的粒度从整个Map降低到了单个桶。


看一段代码,HashMap、Hashtable和ConcurrentHashMap在多线程环境中的行为:


import java.util.HashMap;  
import java.util.Hashtable;  
import java.util.concurrent.ConcurrentHashMap;  
  
public class ThreadSafeHashMapComparison {  
  
    public static void main(String[] args) {  
          
        // 1. HashMap - 非线程安全的示例  
        HashMapExample(new HashMap<>());  
          
        // 2. Hashtable - 线程安全的示例  
        HashtableExample(new Hashtable<>());  
          
        // 3. ConcurrentHashMap - 线程安全的示例,性能更好  
        ConcurrentHashMapExample(new ConcurrentHashMap<>());  
    }  
      
    public static void HashMapExample(HashMap<Integer, String> map) {  
        map.put(1, "One");  
        map.put(2, "Two");  
        map.put(3, "Three");  
          
        // 启动两个线程同时修改map  
        new Thread(() -> {  
            map.put(4, "Four");  
        }).start();  
        new Thread(() -> {  
            map.remove(2);  
        }).start();  
    }  
      
    public static void HashtableExample(Hashtable<Integer, String> hashtable) {  
        hashtable.put(1, "One");  
        hashtable.put(2, "Two");  
        hashtable.put(3, "Three");  
          
        // 启动两个线程同时修改hashtable  
        new Thread(() -> {  
            hashtable.put(4, "Four");  
        }).start();  
        new Thread(() -> {  
            hashtable.remove(2);  
        }).start();  
    }  
      
    public static void ConcurrentHashMapExample(ConcurrentHashMap<Integer, String> concurrentHashMap) {  
        concurrentHashMap.put(1, "One");  
        concurrentHashMap.put(2, "Two");  
        concurrentHashMap.put(3, "Three");  
          
        // 启动两个线程同时修改concurrentHashMap  
        new Thread(() -> {  
            concurrentHashMap.put(4, "Four");  
        }).start();  
        new Thread(() -> {  
            concurrentHashMap.remove(2);  
        }).start();  
    }  
}

趁热打铁,再来看一段代码,使用Java中的ReentrantLockCondition 来 实现一个线程安全的、可扩展的 HashMap 。这个示例中,我们还将展示如何处理更复杂的并发情况,如多个线程同时尝试修改相同的键。


import java.util.HashMap;  
import java.util.Map;  
import java.util.concurrent.locks.Condition;  
import java.util.concurrent.locks.ReentrantLock;  
  
public class ThreadSafeHashMap<K, V> {  
  
    private final Map<K, V> map = new HashMap<>();  
    private final ReentrantLock lock = new ReentrantLock();  
    private final Condition condition = lock.newCondition();  
  
    public V put(K key, V value) {  
        lock.lock();  
        try {  
            // 等待当前线程获取锁后,再执行下面的代码  
            condition.await();  
            // 检查键是否已经存在,如果存在则更新值,否则插入新键值对  
            return map.merge(key, value, (oldValue, newValue) -> newValue);  
        } catch (InterruptedException e) {  
            Thread.currentThread().interrupt();  
            throw new RuntimeException(e);  
        } finally {  
            lock.unlock();  
        }  
    }  
  
    public V remove(K key) {  
        lock.lock();  
        try {  
            // 等待当前线程获取锁后,再执行下面的代码  
            condition.await();  
            return map.remove(key);  
        } catch (InterruptedException e) {  
            Thread.currentThread().interrupt();  
            throw new RuntimeException(e);  
        } finally {  
            lock.unlock();  
        }  
    }  
      
    public static void main(String[] args) {  
        ThreadSafeHashMap<Integer, String> threadSafeHashMap = new ThreadSafeHashMap<>();  
        threadSafeHashMap.put(1, "One");  
        threadSafeHashMap.put(2, "Two");  
        threadSafeHashMap.put(3, "Three");  
          
        // 启动两个线程同时修改map,其中一个线程尝试更新已存在的键,另一个线程尝试删除一个键  
        new Thread(() -> {  
            threadSafeHashMap.put(4, "Four"); // 插入新键值对  
        }).start();  
        new Thread(() -> {  
            threadSafeHashMap.remove(2); // 删除键值对(2,"Two")  
        }).start();  
    }  
}

✔️继承关系方面


HashTable是基于陈旧的 Dictionary 类继承来的。


HashMap 继承的抽象类 AbstractMap 实现了 Map 接口。


ConcurrentHashMap 同样继承了抽象类AbstractMap,并且实现了 ConcurrentMap 接口。


接下来,我们通过代码来展示它们在继承关系方面的区别:


import java.util.HashMap;  
import java.util.Hashtable;  
import java.util.concurrent.ConcurrentHashMap;  
  
public class HashMapVsHashtableVsConcurrentHashMap {  
    public static void main(String[] args) {  
        // 创建一个HashMap实例  
        HashMap<String, Integer> hashMap = new HashMap<>();  
        System.out.println("HashMap继承关系: " + hashMap.getClass().getSuperclass());  
          
        // 创建一个Hashtable实例  
        Hashtable<String, Integer> hashtable = new Hashtable<>();  
        System.out.println("Hashtable继承关系: " + hashtable.getClass().getSuperclass());  
          
        // 创建一个ConcurrentHashMap实例  
        ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();  
        System.out.println("ConcurrentHashMap继承关系: " + concurrentHashMap.getClass().getSuperclass());  
    }  
}

运行上面的代码,输出结果将会显示这三个类在继承关系上的不同。输出结果如下:

  1. HashMap 继承自 AbstractMap
  2. Hashtable 继承自 DictionaryHashtable。这是因为 Hashtable 是遗留类,设计用于Java 1.0,而 Dictionary 是它的超类。
  3. ConcurrentHashMap 也继承自 AbstractMap,与 HashMap 类似。这是因为它的设计目标是为了提供线程安全的哈希表,而不需要额外的线程安全机制。

✔️ 允不允许null值方面


HashTable中,keyvalue 都不允许出现null 值,否则会抛出 NullPointerException 异常。


HashMap 中,null 可以作为键或者值都可以。


ConcurrentHashMap 中,keyvalue 都不允许为null。


✔️为什么ConcurrentHashMap不允许null值?


我们知道,ConcurrentHashMap 在使用时,和 HashMap 有一个比较大的区别,那就是HashMap 中,null 可以作为键或者值都可以。而在 ConcurrentHashMap 中,key value 都不允许为null


那么,为什么呢? 为啥ConcurrentHashMap要设计成这样的呢?


关于这个问题,其实最有发言权的就是ConcurrentHashMap的作者-Doug Lea


大家看一个截图吧。因为原文地址现在不知道怎么搞的打不开了。一张截图大家凑合看着吧。


在这里插入图片描述

主要意思就是说 :


ConcurrentMap (如 ConcurrentHashMapConcurrentSkipListMap ) 不允许使用 null 值的主要原因是,在非并发的Map中(如HashMap),是可以容忍模糊性 (二义性)的,而在并发Map中是无法容忍的


假如说,所有的 Map 都支持 null 的话,那么 map.get(key) 就可以返回 null ,但是,这时候就会存在一个不确定性,当你拿到null的时候,你是不知道他是因为本来就存了一个 null 进去还是说就是因为没找到而返回了null。


在HashMap中,因为它的设计就是给单线程用的,所以当我们map.get(key)返回nul的时候,我们是可以通过map.contains(key)检查来进行检测的,如果它返回true,则认为是存了一个null,否则就是因为没找到而返回了null。


但是,像ConcurrentHashMap,它是为并发而生的,它是要用在并发场景中的,当我们map.get(key)返回null的时候,是没办法通过map.contains(key)检查来准确的检测,因为在检测过程中可能会被其他线程所修改,而导致检测结果并不可靠。


所以,为了让 ConcurrentHashMap 的语义更加准确,不存在二义性的问题,他就不支持null。


✔️ 默认初始容量和扩容机制


HashMap的默认初始容量为16,默认的加载因子为0.75,即当HashMap中元素个数超过容量的75%时,会进行扩容操作。扩容时,容量会扩大为原来的两倍,并将原来的元素重新分配到新的桶中。


Hashtable,默认初始容量为11,默认的加载因子为0.75,即当Hashtable中元素个数超过容量的75%时,会进行扩容操作。扩容时,容量会扩大为原来的两倍加1,并将原来的元素重新分配到新的桶中。


ConcurrentHashMap ,默认初始容量为16,默认的加载因子为0.75,即当ConcurrentHashMap 中元素个数超过容量的75%时,会进行扩容操作。扩容时,容量会扩大为原来的两倍,并会采用分段锁机制,将 ConcurrentHashMap 分为多个段(segment),每个段独立进行扩容操作,避免了整个ConcurrentHashMap 的锁竞争。


✔️遍历方式的内部实现上不同


HashMap 使用 EntrySet 进行遍历,即先获取到 HashMap 中所有的键值对(Entry),然后遍历Entry集合。支持 fail-fast ,也就是说在遍历过程中,若 HashMap的结构被修改(添加或删除元素),则会抛出ConcurrentModificationException,如果只需要遍历 HashMap 中的 keyvalue ,可以使用KeySetValues来遍历。


Hashtable 使用Enumeration进行遍历,即获取Hashtable中所有的key,然后遍历key集合。遍历过程中,Hashtable 的结构发生变化时,Enumeration 会失效。


ConcurrentHashMap 使用分段锁机制,因此在遍历时需要注意,遍历时ConcurrentHashMap 的某人段被修改不会影响其他段的遍历。可以使用EntrySetKeySet或Values来遍历ConcurrentHashMap,其中EntrySet遍历时效率最高。遍历过程中,ConcurrentHashMap的结构发生变化时,不会抛出ConcurrentModificationException异常,但是在遍历时可能会出现数据不一致的情况,因为遍历器仅提供了弱一致性保障。


以下是一个8行4列的表格:

特性/集合类HashMapHashtableConcurrentHashMap
线程安全是,基于方法锁是,基于分段锁
继承关系AbstractMapDictionaryAbstractMap,ConcurrentMap
允许null值K-V都允许K-V都不允许K-V都不允许
默认初始容量161116
默认加载因子0.750.750.75
扩容后容量原来的两倍原来的两倍+1原来的两倍
是否支持fail-fast支持不支持fail-safe

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1359800.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

异步任务判断执行和重复使用实现类

主要是展示一下如何在书写异步任务判断的时候&#xff0c;如何根据返回值类型进行重复使用相同接口里面的不同实现类的方法 /*** 父类接口* **/ public interface Exceutor {String getTaskType();void excetuor(String s); }/*** 异步处理任务的任务类型** author yangziqian…

万界星空科技MES系统中的生产管理

MES系统能够帮助企业实现生产计划管理、生产过程控制、产品质量管理、车间库存管理、项目看板管理等&#xff0c;提高企业制造执行能力。 万界星空MES系统特点&#xff1a; 1. 采用强大数据采集引擎、整合数据采集渠道&#xff08;RFID、条码设备、PLC、Sensor、IPC、PC等&…

【JAVA】异常体系

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 Exception&#xff08;异常&#xff09;: Error: 结语 我的其他博客 前言 在Java编程中&#xff0c;异常处理是一个至关…

python练习3【题解///考点列出///错题改正】

一、单选题 1.【单选题】 ——可迭代对象 下列哪个选项是可迭代对象&#xff08; D&#xff09;&#xff1f; A.(1,2,3,4,5) B.[2,3,4,5,6] C.{a:3,b:5} D.以上全部 知识点补充——【可迭代对象】 可迭代对象&#xff08;iterable&#xff09;是指可以通过迭代&#xff…

揭秘人工智能:探索智慧未来

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 什么是人工智能?二. 人工智能的关键技术2.1 机器学习2.2 深度学习2.1 计算机…

基于web3.js和ganache实现智能合约调用

目的&#xff1a;智能合约发布到本地以太坊模拟软件ganache并完成交互 准备工作&#xff1a; web3.jsganache模拟软件 ganache参数配置 从ganache获取一个url&#xff0c;和一个账号的地址&#xff0c; url直接使用图中的rpc server位置的数据即可 账号address从下列0x开头…

深度学习(Pytorch版本)

零.前置说明 1、code 2、视频 数据预处理实现_哔哩哔哩_bilibili

RoadMap8:C++中类的封装、继承、多态与构造函数

摘要&#xff1a;在本章中涉及C最核心的内容&#xff0c;本文以C中两种基础的衍生数据结构&#xff1a;结构体和类作为引子&#xff0c;从C的封装、继承与多态三大特性全面讲述如何在类这种数据结构进行体现。在封装中&#xff0c;我们讲解了类和结构体的相似性&#xff1b;在继…

2023南京理工大学通信工程818信号系统及数电考试大纲

注&#xff1a;&#xff08;Δ&#xff09;表示重点内容。具体内容详见博睿泽信息通信考研论坛 参考书目&#xff1a; [1] 钱玲&#xff0c;谷亚林&#xff0c;王海青. 信号与系统&#xff08;第五版&#xff09;. 北京&#xff1a;电子工业出版社 [2] 郑君里&#xff0c;应…

Profinet转Modbus网关助你畅享智能工业

Modbus转Profinet网关&#xff08;XD-MDPN100/200&#xff09;具有广泛的应用价值。无论是汽车制造、机械加工还是能源管理&#xff0c;都可以通过使用该网关&#xff0c;实现设备之间的高效通信。其次&#xff0c;Modbus转Profinet网关&#xff08;XD-MDPN100/200&#xff09;…

JVM篇:直接内存

直接内存 直接内存并不是JVM的内存结构&#xff0c;直接内存是操作系统的内存&#xff0c;Java本身并不能对操作系统的内存进行操作&#xff0c;而是通过调用本地方法。直接内存常用于NIO作为缓冲区存在&#xff0c;分配成本较高但是读写性能好&#xff0c;并且不受JVM内存回收…

产品设计的七个步骤详解

我们生活中使用的大大小小的东西都需要进行产品设计&#xff0c;那么什么是产品设计呢&#xff1f;产品设计是指从制定新产品设计任务到开发企业产品样品截止日期的一系列技术任务。让我分享一些产品设计步骤。 业务分析-要做什么业务分析 首先是制作业务流程图&#xff0c;这…

使用 dbgate 在 sealos 上完美管理 mysql pgsql 等数据库

先登录 sealos 创建数据库&#xff0c;可以创建个 pgsql: 再到模版市场启动 dbgate: 配置数据库的连接信息&#xff0c;即可搞定收工 sealos 以kubernetes为内核的云操作系统发行版&#xff0c;让云原生简单普及 laf 写代码像写博客一样简单&#xff0c;什么docker kubernete…

ubuntu18.04+realsenseD455制作TUM数据集

教程目录 一、本机环境二、安装RealSense SDK三、录制rosbag四、制作数据集四、安装ROS-RealSense五、测试数据集一、本机环境 Ubuntu系统ROS系统RealSense18.04melodicD455二、安装RealSense SDK 1、首先注册服务器的公钥 sudo apt-key adv --keyserver keyserver.ubuntu.co…

crontab定时任务不执行的原因

1.crond服务未启动 默认是开启的 2.权限问题 比如&#xff1a;脚本没有x执行权限&#xff0c; 解决方法&#xff1a; 增加执行权限&#xff0c;或者用bash abc.sh的方法执行 也有可能crontab任务所属的用户对某个目录没有写权限&#xff0c;也会失败 3.路径问题 建议使用绝…

【Angular 】Angular 模板中基于角色的访问控制

您是否在Angular模板中实现角色库访问控制&#xff1f;一种方法是通过*ngIf&#xff0c;但我不会选择该路径&#xff0c;因为它将在Angular模板中包含自定义函数&#xff0c;并且很难维护。正确的方法是使用Angular结构指令&#x1f680;. 什么是RBAC&#xff1f; 基于角色的…

【网络技术】【Kali Linux】Wireshark嗅探(七)超文本传送协议(HTTP)

一、实验目的 本次实验使用Wireshark流量分析工具进行网络嗅探&#xff0c;旨在了解超文本传送协议&#xff08;HTTP&#xff09;的工作原理。 二、HTTP协议概述 超文本传送协议&#xff08; H yper T ext T ransfer P rotocol, HTTP&#xff09;是互联网应用层的一个重要协…

Python 面向对象之继承和组合

Python 面向对象之继承和组合 【一】继承 【1】概念 继承是面向对象的三大特征之一继承允许一个类继承另一个类的属性和方法继承可以使代码重用&#xff0c;解决类与类之间代码重复的问题 【2】代码解释 不使用继承&#xff0c;创建豌豆射手类和豌豆的双发射手类 # 豌豆射…

C语言编译器(C语言编程软件)完全攻略(第六部分:VS2017下载地址和安装教程(图解))

介绍常用C语言编译器的安装、配置和使用。 六、VS2017下载地址和安装教程&#xff08;图解&#xff09; 继vs2015版本后&#xff0c;微软又推出了功能更加强大的VS 2017。 Visual Studio 2017 不仅支持 C#、C、Python、Visual Basic、Node.js、HTML、JavaScript等各大编程语言…

KK集团高管变更:陈世欣任总经理,涉无证放贷遭关注,还曾被处罚

近日&#xff0c;KK集团关联公司广东快客电子商务有限公司&#xff08;下称“KK集团”&#xff09;发生工商变更&#xff0c;其中郭惠波不再担任该公司总经理一职&#xff0c;由陈世欣接任。而在早前&#xff0c;陈世欣曾于2020年取代吴悦宁担任总经理职务&#xff0c;2021年7月…