ConcurrentHashMap介绍
ConcurrentHashMap
JDK1.7 版本到 JDK1.8 版本的演变
JDK1.7 版本
采用的是分段锁,每个segment都是独立的,可以并发访问不同的segment,默认是16个segment,所以最多是16个线程并发执行
JDK1.8 版本
移除了segment,锁的粒度变得更加细粒化,锁只在红黑树和链表的节点级别进行,通过CAS进行插入操作,如果某个节点为空,就通过CAS插入节点,如果不为空,就使用sychronized锁定冲突的节点的头结点,进一步减少锁的竞争,提高并发度
为什么ConcurrentHashMap不允许Key,Value为空
为了避免混淆和潜在的开发问题
ConcurrentHashMap是为了并发环境而设计的,在并发访问的时候,如果key-value为 null,可能会发生歧义,比如,在使用get()时,返回值为null,无法确定是 key 为 null 还是 value 为 null。
为了简化实现
如果key或者value允许为 null,会给put, get, containsKey带来一些操作上面的复杂性,例如get操作可能频繁返回null,那就需要额外的逻辑去判断究竟是key为 null 还是 value为 null
如果允许为 null,那就会出现下面的情况
- 是不是因为 value 本身就是 null
- 是不是 key 不存在
- 是不是另一个线程正在更新
调用者很难去判断这些情况,而且还可能导致:
- 重复计算
- 数据不一致
- 并发错误
ConcurrentHashMap 的 get 方法是否需要加锁
不需要加锁
通过volatile,ConcurrentHashMap能够确保get方法的线程安全,即使在写入发生时,读取仍然能够获取最新的数据,不会引发并发的问题
ConcurrentHashMap 部分源码:
static class Node<K,V> implements Map.Entry<K,V> {
volatile V val;
volatile Node<K,V> next;
// ...CODE...
}
具体是通过Unsafe#getXXXXVolatile和利用volatile来修饰节点的 val 和 next 指针来实现的
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
public native Object getObjectVolatile(Object o, long offset);
Unsafe
Unsafe是 Java 提供的内部类,用来执行不安全操作,并提供了直接操作内存和现成的能力
常见的关键功能:
- 直接内存访问:允许直接分配、释放、读写内存
- 对象操作:允许直接操作对象的字段,如获取和设置对象的字段值
- 线程操作:包含暂停和恢复线程、管理锁等
- CAS 操作:提供了
compare-and-swap操作,支持原子性更新操作
与HashMap,HashTable比较
| Map 类型 | 是否允许 null | 原因 |
|---|---|---|
| HashMap | 允许 | 单线程下没有歧义 |
| Hashtable | 不允许 | 历史原因 + 避免歧义 |
| ConcurrentHashMap | 不允许 | 多线程下 null 会导致逻辑不确定 |

浙公网安备 33010602011771号