ConcurrentHashMap源码解读!

本文详细介绍了ConcurrentHashMap,它是JDK5引入的线程安全的高并发HashMap。ConcurrentHashMap在不同版本中经历了结构变化,从1.7的Segment+HashEntry到1.8的数组+链表+红黑树。通过使用CAS和synchronized保证并发安全性,并利用spread方法替换hash()获取哈希值,提高检索效率。文章还探讨了其初始化、扩容策略以及put和get操作的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 .首先先看看为什么要使用ConcurrentHashMap!

首先,ConcurrentHashMap是JDK5中引用的一个线程安全的支持高并发的HashMap集合类。它是弥补了HashMap和HashTable的不足而设计的;

1.HashMap是非线程安全的,在多线程操作环境下,使用Hashmap进行put操作可能会引起死循环,这是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。导致CPU利用率接近100%,所以在并发情况下一般不使用HashMap。

2.HashTable效率较极低,如线程1使用put插入元素,而线程2,什么都做不了,必须等待线程1释放锁。

3. 故而ConcurrentHashMap 由一个 Segment 组成,Segment 代表”部分“或”一段“的,所以很多地方都会将其描述为分段锁。每次需要加锁的操作锁住的是一个 Segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
其使用了分段锁技术,
所谓分段锁技术就是数据分段,然后容器里就会有多把锁,每一把锁用于锁容器其中一部分数据,		那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,
从而可以有效的提高并发访问效率。

4.用 Volatile 关键字约束变量,从而协调读写线程间的内存可见性

由于内存可见性问题,未正确同步的情况下,写线程写入的值可能并不为后续的读线程可见。

根据 Java 内存模型,对 同一个 volatile 变量的写 / 读操作可以确保:写线程写入的值,能够被之后未加锁的读线程“看到”。

2.实现结构

看它的实现和继承:

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable

同HashMap一样,也是继承了AbstractMap接口;实现了ConcurrentMap类和 Serializable类。

ConcurrentHashMap底层=数组+链表+红黑树。红黑树可以使hash散列更均匀。

如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);
jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),大大提高了检索效率。

先看java1.7中的结构:
每段数据对应着每个segment,加锁时,
到jdk1.8:
在这里插入图片描述
为了进一步提高并发性,在jdk1.8中则取消segment的概念,虽然源码保留了,但是只是为了考虑兼容性。采用table来保存数据
transient volatile HashEntry<K,V> table
采用table数组元素作为锁,相对于jdk1.7,实现了对每一行数据进行加锁,进一步减少并发冲突的概率。

同时使用CAS + synchronized的方式来保证并发更新的安全。**CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术。

几个重要属性:

private static final int MAXIMUM_CAPACITY = 1 << 30; node节点的最大容量

private static final int DEFAULT_CAPACITY = 16;  默认table初始化容量是16,且扩容必须是2的幂次方 
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;  数组最大值。
 private static final int DEFAULT_CONCURRENCY_LEVEL = 16;  默认并发量,默认是16.

这个是segment数量,也就是说 ConcurrentHashMap 默认值有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。

private static final float LOAD_FACTOR = 0.75f;  

static final int TREEIFY_THRESHOLD = 8;  链表 转红黑树的阈值界限

static final int UNTREEIFY_THRESHOLD = 6;

static final int MIN_TREEIFY_CAPACITY = 64;
transient volatile Node<K,V>[] table;   装载Node节点的table.默认为null,采用懒加载的方式,直到第一次插入数据的时候才会进行初始化操作,数组的大小总是为2的幂次方。

 privat
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值