当前位置: 首页 > news >正文

Java ConcurrentHashMap 深度解析

Java ConcurrentHashMap 深度解析

引言

在多线程编程中,HashMap的线程不安全性是一个众所周知的问题。当多个线程同时访问和修改HashMap时,可能会导致数据不一致、无限循环等严重问题。为了解决这个问题,Java提供了多种线程安全的Map实现,其中ConcurrentHashMap是最优秀的选择之一。

什么是ConcurrentHashMap?

ConcurrentHashMap是Java并发包(java.util.concurrent)中的一个线程安全的哈希表实现。它继承了AbstractMap类并实现了ConcurrentMap接口,提供了高效的并发访问能力。

主要特点

  • 线程安全:支持多线程并发读写操作
  • 高性能:采用分段锁机制,减少锁竞争
  • 无锁读取:大部分读操作不需要加锁
  • 弱一致性:迭代器具有弱一致性,不会抛出ConcurrentModificationException

ConcurrentHashMap的演进历程

JDK 1.7 版本 - 分段锁机制

在Java 7中,ConcurrentHashMap采用了分段锁(Segment)的设计思想:

// JDK 1.7 的简化结构
static final class Segment<K,V> extends ReentrantLock implements Serializable {transient volatile HashEntry<K,V>[] table;transient int count;transient int modCount;transient int threshold;final float loadFactor;
}

工作原理

  • 将整个哈希表分成多个段(Segment)
  • 每个段都是一个独立的哈希表,有自己的锁
  • 不同段之间的操作可以并发进行
  • 默认分为16个段,支持最多16个线程同时写入

JDK 1.8+ 版本 - CAS + synchronized

Java 8对ConcurrentHashMap进行了重大重构,抛弃了分段锁机制:

// JDK 1.8+ 的核心数据结构
transient volatile Node<K,V>[] table;
private transient volatile Node<K,V>[] nextTable;
private transient volatile int sizeCtl;

新的设计特点

  • 使用Node数组 + 链表/红黑树的结构
  • 采用CAS操作进行无锁更新
  • 使用synchronized锁定单个桶(bucket)
  • 当链表长度超过8时转换为红黑树

核心实现原理

1. 数据结构

static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;volatile V val;volatile Node<K,V> next;
}// 树节点
static final class TreeNode<K,V> extends Node<K,V> {TreeNode<K,V> parent;TreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev;boolean red;
}

2. PUT操作流程

public V put(K key, V value) {return putVal(key, value, false);
}final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;// 1. 初始化表if (tab == null || (n = tab.length) == 0)tab = initTable();// 2. 桶为空,使用CAS插入else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))break;}// 3. 正在扩容else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);// 4. 桶不为空,使用synchronizedelse {V oldVal = null;synchronized (f) {// 插入链表或树// ...}}}addCount(1L, binCount);return null;
}

3. GET操作流程

public V get(Object key) {Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;int h = spread(key.hashCode());if ((tab = table) != null && (n = tab.length) > 0 &&(e = tabAt(tab, (n - 1) & h)) != null) {// 1. 检查头节点if ((eh = e.hash) == h) {if ((ek = e.key) == key || (ek != null && key.equals(ek)))return e.val;}// 2. 特殊节点处理(树节点或转发节点)else if (eh < 0)return (p = e.find(h, key)) != null ? p.val : null;// 3. 遍历链表while ((e = e.next) != null) {if (e.hash == h &&((ek = e.key) == key || (ek != null && key.equals(ek))))return e.val;}}return null;
}

性能优化技巧

1. 合理设置初始容量

// 根据预期元素数量设置初始容量
int expectedSize = 1000;
int initialCapacity = (int)(expectedSize / 0.75) + 1;
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(initialCapacity);

2. 使用计算方法

// 使用compute系列方法,减少多次查找
map.compute("key", (k, v) -> {if (v == null) {return "new value";} else {return v + " updated";}
});// 原子性累加
map.compute("counter", (k, v) -> (v == null) ? 1 : v + 1);

3. 批量操作

// 使用forEach进行并行遍历
map.forEach(1000, (key, value) -> {// 处理逻辑processKeyValue(key, value);
});// 使用reduce进行并行归约
String result = map.reduce(1000, (key, value) -> key + "=" + value,(s1, s2) -> s1 + "," + s2
);

最佳实践

1. 选择合适的容器

// 读多写少的场景
Map<String, String> readMostly = new ConcurrentHashMap<>();// 写多读少的场景,考虑使用其他并发容器
// 或者使用读写锁保护的HashMap

2. 避免使用size()方法

// 不推荐:size()方法代价较高
if (map.size() > 0) {// 处理逻辑
}// 推荐:使用isEmpty()
if (!map.isEmpty()) {// 处理逻辑
}

3. 正确使用迭代器

// 弱一致性迭代器,不会抛出ConcurrentModificationException
for (Map.Entry<String, String> entry : map.entrySet()) {// 迭代过程中的修改不会立即反映到迭代器中System.out.println(entry.getKey() + " -> " + entry.getValue());
}

与其他Map实现的比较

特性HashMapHashtableConcurrentHashMap
线程安全
null键值支持不支持不支持
性能低(全局锁)高(细粒度锁)
迭代器fail-fastfail-fast弱一致性

注意事项

1. 复合操作的原子性

// 错误:两个操作之间可能被其他线程修改
if (!map.containsKey(key)) {map.put(key, value);
}// 正确:使用原子性方法
map.putIfAbsent(key, value);

2. 大小计算的准确性

// size()和isEmpty()的结果可能不是完全准确的
// 在高并发环境下,这些方法返回的是近似值
long size = map.mappingCount(); // 推荐使用mappingCount()

总结

ConcurrentHashMap是Java并发编程中不可或缺的工具类。它通过精巧的设计实现了高性能的并发访问,在JDK 8+版本中更是通过CAS和synchronized的组合进一步提升了性能。

http://www.lryc.cn/news/623926.html

相关文章:

  • 【LeetCode 热题 100】(八)二叉树
  • EC11编码器
  • 集成电路学习:什么是SIFT尺度不变特征变换
  • 43 C++ STL模板库12-容器4-容器适配器-堆栈(stack)
  • 基于DSP+ARM+FPGA架构的储能协调控制器解决方案,支持全国产化
  • 电子电气架构 --- 自动驾驶汽车的下一步发展是什么?
  • 下降路径最小和
  • 【网络通信】TCP/IP 协议全方位解析​
  • java如何把字符串数字转换成数字类型
  • OpenCV 图像处理核心技术:边界填充、算术运算与滤波处理实战
  • android aidl相关学习
  • 常用的SQL语句
  • java16学习笔记
  • topographic terrain
  • AMBA-AXI and ACE协议详解(七)
  • 计算机网络---跳板机与堡垒机
  • 如何理解事件循环和JS的异步?
  • Oracle查看历史会话信息视图介绍
  • 深入理解QFlags:Qt中的位标志管理工具
  • Springboot项目3种视图(JSP、Thymeleaf、Freemarker)演示
  • 【SpringBoot】SpringBoot的异步任务、邮件发送、定时任务
  • Spring Bean 的生命周期:从创建到销毁的完整旅程​
  • 好看的个人导航系统多模板带后台
  • React端到端测试
  • 通达信【牛股妖股埋伏】副图+选股指标
  • Shell脚本-while循环应用案例
  • nn.Module模块介绍
  • 计算机视觉(一):nvidia与cuda介绍
  • OpenMemory MCP发布!AI记忆本地共享,Claude、Cursor一键同步效率翻倍!
  • 【Linux】文件基础IO