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

在这里插入图片描述


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.hqwc.cn/news/324300.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年12月23日~2024年1月5日周报(调试CNN-RWI代码、继续研读论文)

一、前言 上周对CNN-RWI论文进行了初步研读&#xff0c;但是只是了解了大概&#xff0c;很多问题也只是停留在表面&#xff0c;没有进行深入思考。 本周带着一系列问题继续研读CNN-RWI论文&#xff0c;并对代码进行初步学习。 问题描述&#xff1a; ①为什么阈值大小设置为网格…

案例092:基于微信小程序的二手闲置交易市场系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

深圳易图讯科技VR三维电子沙盘系统

易图讯VR三维电子沙盘系统是一种结合虚拟现实技术的地理信息系统。它通过高精度三维模型&#xff0c;真实再现了地理环境、建筑布局和地形地貌。用户可通过VR设备沉浸式体验这一虚拟世界&#xff0c;进行各种交互操作&#xff0c;如缩放、旋转、移动等。系统还支持实时数据更新…

【docker】—— Docker 简介

目录 &#xff08;一&#xff09;容器技术发展史 1、Jail 时代 2、云时代 3、云原生时代 &#xff08;二&#xff09;编排与容器的技术演进之路 1、DockerClient 2、RUNC&Shim 3、CRI-Containerd 4、CRI-O 5、Containerd &#xff08;三&#xff09;Docker 简介…

【OJ】单链表刷题

力扣刷题 1. 反转链表&#xff08;206&#xff09;1.1 题目描述1.2 题目分析1.2.1 头插法1.2.2 箭头反转 1.3 题目代码1.3.1 头插入1.3.2 箭头反转 2.合并两个有序链表&#xff08;21&#xff09;2.1 题目描述2.2 题目分析2.3 题目代码 1. 反转链表&#xff08;206&#xff09;…

【Docker】从Logo开始了解什么是docker

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是2024年第x篇文章&#xff0c;此篇文章是《Docker容器》序列文章&#xff0c;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 在实际工作中&#xff0c;实际上有接触过容…

关键字:const关键字

在 Java 中&#xff0c;const关键字用于常量声明。常量是在程序的整个生命周期中不会改变的值。 以下是使用const关键字声明常量的示例&#xff1a; 在上述示例中&#xff0c;创建了一个名为Constants的类&#xff0c;其中包含一个静态内部类ConstValue。在ConstValue类中&am…

如何使用ArcGIS Pro转换单个点坐标

坐标转换作为基础的功能&#xff0c;一般的GIS软件都支持&#xff0c;大多数情况下&#xff0c;我们是转换整个图层&#xff0c;如果想要转换单个坐标点&#xff0c;在ArcGIS Pro内也是支持的&#xff0c;这里为大家介绍一下转换方法&#xff0c;希望能对你有所帮助。 拾取坐标…

华为交换机hybrid接口配置

SW1配置 vlan batch 10 20 100interface GigabitEthernet0/0/1port hybrid pvid vlan 10port hybrid untagged vlan 10 100interface GigabitEthernet0/0/2port hybrid pvid vlan 20port hybrid untagged vlan 20 100interface GigabitEthernet0/0/3port hybrid tagged vlan 1…

bat批处理文件_命令汇总(2)

文章目录 1、换行2、返回上一级目录cd..3、隐藏指令回显echo off4、开启指令回显echo on5、用关闭echo off指令本身的回显6、echo提示信息 1、换行 cd.. echo. echo. echo. pause2、返回上一级目录cd… 3、隐藏指令回显echo off echo off echo hello1 echo hello2 pause4、开…

普通BUG

IDEA包折叠 如果自动紧凑包名,则有些时候创建新包或类的时候不能达到想要的摆放层级关系,此时右上角搜索按钮搜hide middle,关掉紧凑即可,然后既可以每层一个包不折叠. 效果: 20240105println输出多个参数 int a 10;int b 20;报错println是可以输出多个参数的,但不支持直接用…

克服幻觉:提升语言模型在自然语言处理中的准确性与可靠性

随着语言模型&#xff08;LLM&#xff09;在自然语言处理&#xff08;NLP&#xff09;中的应用日益普及&#xff0c;它们在文本生成、机器翻译、情感分析等许多任务中展现出惊人的能力。然而&#xff0c;这些模型也常常显示出一个被称作“幻觉”&#xff08;hallucination&…