HashTable, HashMap, ConcurrentHashMap 之间的区别

HashMap: 线程不安全. key 允许为 null。

Hashtable: 线程安全. 使用 synchronized 锁 Hashtable 对象, 效率较低. key 不允许为 null.。 只是简单的把关键方法上加上了 synchronized 关键字。如 get 和 set ,这相当于直接针对 Hashtable 对象本身加锁,如果多线程访问同一个 Hashtable 就会直接造成锁冲突.。size 属性也是通过 synchronized 来控制同步, 也是比较慢的。一旦触发扩容, 就由该线程完成整个扩容过程. 这个过程会涉及到大量的元素拷贝, 效率会非常低。当多个线程进行一些更复杂的操作,比如判定get 的值就是xxx,再进行put 就会产生线程安全问题。例如

复制代码
Hashtable<Integer, Integer> hash = new Hashtable<>();
if(hash.get(1) == null){
    hash.put(1, 2);
}

虽然get是加锁的,但这里的 if 和 put 不是原子的,当多个线程线程同时进行这个操作的话,就会导致前一个线程判断为空,另一个线程也判断为空,这就导致这个操作进行了多次,但实际上只需要进行一次,这就出现了线程安全问题。

ConcurrentHashMap: 线程安全. 使用 synchronized 锁每个链表头结点, 锁冲突概率低, 充分利用CAS 机制. 优化了扩容方式. key 不允许为 null。 相比于 Hashtable 做出了一系列的改进和优化. 以 Java1.8 为例:

1)读操作没有加锁(但是使用了 volatile 保证从内存读取结果), 只对写操作进行加锁.

2)加锁的方式仍然是是用 synchronized, 但是不是锁整个对象, 而是 "锁桶" (用每个链表的头结点作为锁对象), 减小了锁的粒度,大大降低了锁冲突的概率.。

3)充分利用 CAS 特性. 比如 size 属性通过 CAS 来更新. 避免出现重量级锁的情况.。

4)优化了扩容方式: 化整为零 ,发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去,扩容期间, 新老数组同时存在。后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程, 每个操作负责搬运一小部分元素,搬完最后一个元素再把老数组删掉,这个期间, 插入只往新数组加, 查找需要同时查新数组和老数组。

ConcurrentHashMap每个哈希桶都有一把锁,只有两个线程同时访问同一个哈希桶才会发生锁冲突。

相关推荐
唐叔在学习17 分钟前
【唐叔学算法】第19天:交换排序-冒泡排序与快速排序的深度解析及Java实现
java·算法·排序算法
music0ant21 分钟前
Idea 配置环境 更改Maven设置
java·maven·intellij-idea
记得开心一点嘛36 分钟前
Nginx与Tomcat之间的关系
java·nginx·tomcat
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
数据小小爬虫1 小时前
Python爬虫获取AliExpress商品详情
开发语言·爬虫·python
小爬虫程序猿1 小时前
利用Python爬虫速卖通按关键字搜索AliExpress商品
开发语言·爬虫·python
王伯爵1 小时前
<packaging>jar</packaging>和<packaging>pom</packaging>的区别
java·pycharm·jar
一朵好运莲1 小时前
React引入Echart水球图
开发语言·javascript·ecmascript
Eiceblue1 小时前
使用Python获取PDF文本和图片的精确位置
开发语言·python·pdf
Q_19284999061 小时前
基于Spring Boot的个人健康管理系统
java·spring boot·后端