ConcurrentHashMap的前世今生

本文深入探讨了Java中内置锁与显示锁的概念区别,详细解释了HashMap与ConcurrentHashMap的数据结构及工作原理,并对比了两者之间的主要差异。此外,还介绍了Java8中ConcurrentHashMap的改进之处。

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

引言:
首先需要了解的是内置锁和显示锁
内置锁也叫对象锁,关键字为 synchronized
显示锁就是Lock及其子类
线程不安全的HashMap
数据结构
    由哈希表实现,数据结构构成为数组+链表+红黑树,解决哈希冲突的方案为拉链法
寻址方式
    通过Integer.highestOneBit算出比指定整数小的最大的2^N值,其实现方法如下。
public static int highestOneBit(int i) {
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return i - (i >>> 1);
}
    Java8 hashcode计算方式:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

Java 7基于分段锁的ConcurrentHashMap

ConcurrentHashMap最外层是一个Segment的数组。每个Segment包含一个与HashMap数据结构差不多的链表数组。

同步方式

Segment继承自ReentrantLock,所以我们可以很方便的对每一个Segment上锁。

ReentrantLock内部实现:

CAS状态:通过是否修改成功来判断是否拿到锁
等待队列:
等待队列中的线程都要做park操作

它使用了自旋锁,如果tryLock获取锁失败,说明锁被其它线程占用,此时通过循环再次以tryLock的方式申请锁。如果在循环过程中该Key所对应的链表头被修改,则重置retry次数。如果retry次数超过一定值,则使用lock方法申请锁。

这里使用自旋锁是因为自旋锁的效率比较高,但是它消耗CPU资源比较多,因此在自旋次数超过阈值时切换为互斥锁。

ConcurrentHashMap与HashMap相比,有以下不同点

ConcurrentHashMap线程安全,而HashMap非线程安全

HashMap允许Key和Value为null,而ConcurrentHashMap不允许

HashMap不允许通过Iterator遍历的同时通过HashMap修改,而ConcurrentHashMap允许该行为,并且该更新对后续的遍历可见


Java 8基于CAS的ConcurrentHashMap

1、摒弃了分段锁的方案,而是直接使用数组元素作为锁。
2、Java 8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(long(N)))

寻址方式:
    将数组强制初始化为2的幂次,用hash&(length-1)取模,提升了运算速度
同步方式:
    直接使用桶元素作为锁(此处为内置锁)
### ConcurrentHashMap 使用指南 ConcurrentHashMapJava 提供的一种线程安全的 Map 实现,专为高并发环境设计。相比传统的 HashMap 和 Hashtable,它在多线程访问时表现出更高的性能和安全性。通过分段锁机制与 CAS(Compare and Swap)操作,ConcurrentHashMap 能够支持多个线程同时读而无需全局加锁。 #### 线程安全特性 ConcurrentHashMap 的线程安全特性来源于其内部实现机制。不同于 Hashtable 使用的 synchronized 方法进行全局加锁,ConcurrentHashMap 在 JDK 1.7 及之前版本中采用 **分段锁(Segment Locking)** 技术[^3]。每个 Segment 相当于一个独立的 HashTable,允许多个线程同时访问不同的 Segment,从而减少锁竞争,提高并发性能。 在 JDK 1.8 中,ConcurrentHashMap 进一步优化了其实现,使用 **桶锁(Bucket-level Locking)** 和 **红黑树结构** 来提升性能。此时,锁的粒度更细,仅对当前操作的桶进行加锁,进一步提高了并发能力[^5]。 #### 常用方法与并发操作 ConcurrentHashMap 提供了一系列线程安全的操作方法,例如: - `putIfAbsent(K key, V value)`:只有当指定键不存在或映射值为 null 时才插入键值对。 - `computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)`:如果键存在,则根据提供的函数重新计算该键的值。 - `compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)`:无论键是否存在,都尝试重新计算其映射值。 - `forEach(BiConsumer<? super K, ? super V> action)`:对每个键值对执行指定操作。 这些方法可以在不使用外部同步的情况下安全地用于多线程环境中[^2]。 #### 示例代码 以下是一个简单的示例,展示如何在 Spring Boot 应用中使用 ConcurrentHashMap 管理共享数据: ```java import java.util.concurrent.ConcurrentHashMap; public class UserService { private final ConcurrentHashMap<String, Integer> userCountMap = new ConcurrentHashMap<>(); public void incrementUserCount(String department) { userCountMap.compute(department, (key, oldValue) -> (oldValue == null) ? 1 : oldValue + 1); } public int getUserCount(String department) { return userCountMap.getOrDefault(department, 0); } } ``` 上述代码中的 `compute` 方法确保在多线程环境下对用户计数的更新是线程安全的,无需额外的同步控制[^4]。 #### 适用场景 ConcurrentHashMap 非常适合以下场景: - **缓存系统**:如 Web 应用中缓存用户信息、会话状态等。 - **计数器**:统计在线人数、请求次数等。 - **任务调度**:管理待处理任务队列或资源池。 - **配置中心**:存储可动态更新的配置项。 由于其良好的并发性能,ConcurrentHashMap 成为了构建高性能服务端应用的重要组件之一。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值