👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中... 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
📚欢迎订阅专栏,专栏名《在2B工作中寻求并发是否搞错了什么》
什么是ConcurrentHashMap?
ConcurrentHashMap 是 Java 中线程安全的哈希表实现,支持高并发读写操作。与 Hashtable 相比,它通过分段锁(JDK1.7)或 CAS + synchronized(JDK1.8)实现更细粒度的锁控制,兼顾性能与线程安全。
下图为JDK1.7和1.8的ConcurrentHashMap:
JDK1.8:
让我们也对比下JDK1.7和JDK1.8的区别吧!
对比维度 | JDK 1.7 | JDK 1.8 |
---|---|---|
数据结构 | Segment 数组(分段锁) + HashEntry 数组 + 链表 | Node 数组 + 链表/红黑树(无 Segment) |
锁机制 | 使用 ReentrantLock 锁住整个 Segment | 使用 synchronized 锁单个桶(链表头节点或红黑树根节点),结合 CAS 无锁操作 |
并发粒度 | 锁粒度较粗(Segment 级别,默认 16 个 Segment) | 锁粒度更细(桶级别,并发度由数组长度动态决定) |
哈希冲突处理 | 仅链表结构 | 链表长度 ≥8 时转为红黑树(优化查询效率) |
扩容机制 | 每个 Segment 独立扩容(头插法) | 多线程协同扩容(尾插法),避免链表死循环 |
size() 实现 | 遍历所有 Segment 统计(需多次加锁,不精确) | 基于 CounterCell 的分段计数(无锁,高效且精确) |
查询效率 | O(n) 链表遍历 | O(1) 链表或 O(logN) 红黑树 |
内存占用 | 较高(Segment 结构额外开销) | 更低(简化结构,去除了 Segment 层) |
线程安全设计 | 分段锁隔离写操作 | CAS + synchronized 精细化控制 |
典型适用场景 | 中等并发场景 | 高并发、大规模数据场景 |
简单使用ConcurrentHashMap
构造方法
那让我们先从构造方法说起:
// 默认构建:默认初始容量 16,负载因子 0.75
ConcurrentHashMap<>();
// 指定初始容量:根据初始容量自动计算扩容阈值。
ConcurrentHashMap(int initialCapacity)
// 完整参数构造:concurrencyLevel 仅为了兼容旧版本,JDK 1.8+ 中实际并发控制通过 CAS + synchronized 实现。
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
// 将给定 Map 的键值对复制到新实例中。
ConcurrentHashMap(Map<? extends K, ? extends V> m)
简单的例子
让我们从一个简答的例子来了解ConcurrentHashMap的使用吧!之后再具体说说各个方法:
public static void main(String[] args) {
// 1. 初始化
ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>();
// 2. 插入数据(线程安全)
scores.put("Alice", 90);
scores.put("Bob", 85);
// 3. 原子操作:若不存在则插入
scores.putIfAbsent("Charlie", 100); // 只有"Charlie"不存在时才会插入
// 4. 原子更新:线程安全的累加(经典计数场景)
scores.compute("Alice", (key, oldValue) -> {
if (oldValue == null) return 1;
return oldValue + 10; // 将 Alice 的分数加10
});
// 5. 线程安全读取
System.out.println("Bob's score: " + scores.get("Bob"));
// 6. 遍历(弱一致性迭代器)
scores.forEach((name, score) ->
System.out.println(name + ": " + score)
);
// 7. 合并操作(原子性合并值)
scores.merge("Bob", 5, Integer::sum); // 如果 Bob 存在,分数+5
}
添加元素
put(K key, V value)
- 功能:插入键值对(若键已存在则覆盖旧值)。
- 线程安全机制:JDK8+ 使用
synchronized
锁定哈希桶的头节点(桶级别锁)。 - 适用场景:简单的键值插入或覆盖。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 10); // 插入 apple=10
map.put("apple", 20); // 覆盖为 apple=20
putIfAbsent(K key, V value)
- 功能:仅当键不存在时才插入值(原子操作)。
- 线程安全机制:基于 CAS 或
synchronized
保证原子性。 - 适用场景:避免重复初始化(如缓存懒加载)。`
// 多线程安全地初始化
map.putIfAbsent("banana", 5); // 若 banana 不存在,则插入 5
map.putIfAbsent("banana", 10); // 此处不会覆盖,值仍为 5
compute(K key, BiFunction remappingFunction)
- 功能:原子性地根据旧值计算新值(允许删除或更新)。
- 线程安全机制:锁定当前桶,保证计算过程的原子性。
- 适用场景:复杂更新逻辑(如累加、条件删除)。
// 原子累加:若存在则 +5,若不存在则设为 0
map.compute("apple", (key, oldVal) -> {
return (oldVal == null) ? 0 : oldVal + 5;
});
computeIfAbsent(K key, Function mappingFunction)
- 功能:若键不存在,则通过函数生成值并插入。
- 线程安全机制:仅在键不存在时触发计算,避免重复初始化。
- 适用场景:懒加载昂贵资源(如缓存、数据库连接)。
// 多线程下只初始化一次
map.computeIfAbsent("orange", k -> {
// 模拟耗时操作(如从数据库加载)
return fetchPriceFromDB(k);
});
merge(K key, V value, BiFunction remapFunction)
- 功能:合并新值和旧值(若键存在则合并,否则直接插入)。
- 线程安全机制:原子操作,类似
compute
但更简洁。 - 适用场景:合并统计(如计数、求和)。
// 线程安全的计数
map.merge("apple", 1, (oldVal, newVal) -> oldVal + newVal); // 若存在则 +1,否则设为 1
replace(K key, V oldValue, V newValue)
- 功能:仅当键的当前值等于
oldValue
时,才替换为newValue
。 - 线程安全机制:基于 CAS 或锁的原子操作。
- 适用场景:条件更新(类似乐观锁)。
// 只有当前值是 20 时才更新为 30
map.replace("apple", 20, 30);
获取元素
get(Object key)
功能:根据键获取对应的值。
线程安全机制:完全无锁,依赖
volatile
关键字保证可见性。特点:
- 如果键不存在,返回
null
。 - 由于弱一致性,可能无法立即反映其他线程的最新修改(但最终会一致)。
- 如果键不存在,返回
适用场景:快速读取键对应的值,无需同步。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 10);
Integer value = map.get("apple"); // 返回 10
Integer missing = map.get("banana"); // 返回 null
getOrDefault(Object key, V defaultValue)
- 功能:获取键对应的值,若键不存在则返回默认值。
- 线程安全机制:与
get
相同(无锁)。 - 优点:避免因键不存在而直接返回
null
导致的空指针问题。 - 适用场景:需要默认值的读取操作。
int count = map.getOrDefault("banana", 0); // 若不存在,返回 0
containsKey(Object key)
- 功能:检查键是否存在。
- 线程安全机制:无锁,通过哈希表结构快速定位。
- 注意:
- 与
get
类似,结果可能受并发修改影响(弱一致性)。 - 若需要原子性检查是否存在并操作,应使用
putIfAbsent
或compute
等方法。
- 与
if (map.containsKey("apple")) {
// 注意:此处其他线程可能已经删除了 "apple"
System.out.println("Apple exists!");
}
containsValue(Object value)
功能:检查值是否存在(遍历整个哈希表)。
线程安全机制:无锁,但需要遍历所有桶。
缺点:
- 性能较差(时间复杂度 O(n))。
- 结果可能不一致(遍历过程中其他线程可能修改数据)。
适用场景:低频的全局值检查。
boolean hasValue = map.containsValue(10); // 检查是否存在值为 10 的条目
forEach(BiConsumer<? super K, ? super V> action)
- 功能:遍历所有键值对。
- 线程安全机制:弱一致性迭代器,遍历时可能跳过或包含并发修改的条目。
- 特点:不会抛出
ConcurrentModificationException
。
map.forEach((key, value) ->
System.out.println(key + ": " + value)
);
search(Function<Map.Entry<K, V>, ? extends U> searchFunction)
- 功能:并行搜索符合条件的条目,返回第一个匹配结果。
- 线程安全机制:遍历时无锁,但结果可能受并发修改影响。
- 适用场景:高效并行查找。
// 查找第一个值为 20 的键
String result = map.search(1, (k, v) -> v == 20 ? k : null);
mappingCount()
- 功能:返回映射的总条目数(
long
类型)。 - 线程安全机制:基于无锁统计,近似值但比
size()
更准确。 - 注意:优先使用
mappingCount()
替代size()
,避免size()
返回int
溢出问题。
long count = map.mappingCount(); // 总条目数(近似值)
删除元素
remove(Object key)
- 功能:根据键删除对应的键值对。
- 线程安全机制:锁定哈希桶的头节点(
synchronized
),保证原子性。 - 返回值:被删除的值(若键存在),否则返回
null
。 - 特点:简单删除,不关心旧值。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 10);
Integer removedValue = map.remove("apple"); // 返回 10
Integer noKey = map.remove("banana"); // 返回 null
remove(Object key, Object value)
- 功能:仅当键的当前值与指定值匹配时,才删除键值对(原子操作)。
- 线程安全机制:基于 CAS 或锁的原子性检查。
- 返回值:删除成功返回
true
,否则返回false
。 - 适用场景:乐观锁式删除(类似“检查再删除”的原子操作)。
map.put("apple", 10);
boolean isRemoved = map.remove("apple", 10); // true(键存在且值匹配)
boolean notRemoved = map.remove("apple", 5); // false(值不匹配)
computeIfPresent(K key, BiFunction remappingFunction)
- 功能:若键存在,则根据旧值计算新值(新值可为
null
,表示删除键)。 - 线程安全机制:锁定当前桶,保证计算和更新的原子性。
- 适用场景:条件删除或更新(例如:根据旧值逻辑删除)。
map.put("apple", 10);
// 若 apple 存在且值 >=10,则删除(返回 null)
map.computeIfPresent("apple", (k, v) -> v >= 10 ? null : v);
// 结果:apple 被删除
replace(K key, V oldValue, V newValue)
- 功能:仅当键的当前值等于
oldValue
时,替换为newValue
。 - 线程安全机制:原子操作(CAS 或锁)。
- 返回值:替换成功返回
true
,否则false
。 - 扩展用法:若
newValue
设为null
,可实现条件删除。
map.put("apple", 10);
boolean replaced = map.replace("apple", 10, 20); // true(替换为 20)
boolean deleted = map.replace("apple", 20, null); // true(替换为 null,等同于删除)
clear()
- 功能:清空所有键值对。
- 线程安全机制:逐步清空每个哈希桶(非原子性,但线程安全)。
- 注意:清空操作期间,其他线程仍可并发读写未处理的桶。
map.clear(); // 清空所有条目
后话
怎么样,聪明的你对ConcurrentHashMap有所了解呢?
没有关系,下一篇才是重中之中!主播将会邀请与您一起共同阅读ConcurrentHashMap。
小手手点上关注,跟上主播的节奏!!