1. 什么是 ConcurrentHashMap
?
ConcurrentHashMap
是 Java 中 线程安全的 HashMap,位于 java.util.concurrent
包下。
它解决了 HashMap
在多线程下不安全、Collections.synchronizedMap()
性能低的问题。
核心优势:高并发读写性能,读操作完全不加锁,写操作只锁部分数据。
2. 为什么不用 HashMap
或 synchronizedMap
?
方案 | 问题 |
| 多线程下可能死循环(JDK 1.7)、数据错乱 |
| 所有操作都加 |
| ✅ 分段锁 / CAS + synchronized(JDK 1.8+),读不加锁,写并发高 |
3. 底层原理(JDK 1.8+)
数组 + 链表 + 红黑树(和
HashMap
一样)CAS + synchronized:写操作使用
synchronized
锁住链表头或红黑树根节点,粒度更小读操作不加锁:利用
volatile
保证内存可见性
结果:高并发下性能远超 synchronizedMap
4. 案例 1:高频缓存系统(读多写少)
场景:用户信息缓存,频繁读取,偶尔更新。
import java.util.concurrent.ConcurrentHashMap; public class UserCache { // ✅ 线程安全的缓存 private static final ConcurrentHashMap<Long, User> userCache = new ConcurrentHashMap<>(); // 模拟数据库查询 private static User findUserFromDB(Long id) { return new User(id, "用户" + id, 20 + id.intValue()); } // 获取用户(高频读) public static User getUser(Long id) { User user = userCache.get(id); if (user == null) { user = findUserFromDB(id); userCache.put(id, user); // 偶尔写 } return user; } // 更新用户(低频写) public static void updateUser(User user) { userCache.put(user.getId(), user); } }
优势:
多个线程同时
getUser()
不互斥,性能极高put
只锁住对应桶(bucket),不影响其他 key 的读写
5. 案例 2:计数统计(高并发写)
场景:统计每个商品的访问次数,多个线程同时增加计数。
public class ProductViewCounter { // key: 商品ID, value: 访问次数 private static final ConcurrentHashMap<Long, Long> viewCount = new ConcurrentHashMap<>(); public static void incrementView(Long productId) { // ✅ putIfAbsent + compute 原子操作 viewCount.compute(productId, (key, count) -> count == null ? 1 : count + 1); } // 获取访问量 public static Long getViewCount(Long productId) { return viewCount.getOrDefault(productId, 0L); } }
为什么用 compute
?
compute
是原子操作,避免:
Long count = viewCount.get(productId); viewCount.put(productId, count + 1); // 中间可能被其他线程修改
替代方案:LongAdder
(更高性能)
private static final ConcurrentHashMap<Long, LongAdder> viewAdder = new ConcurrentHashMap<>(); public static void incrementView(Long productId) { viewAdder.computeIfAbsent(productId, k -> new LongAdder()).increment(); }
6. 案例 3:限流器(Rate Limiter)
场景:限制每个 IP 每秒最多 10 次请求。
public class RateLimiter { // key: IP, value: 请求次数 private static final ConcurrentHashMap<String, Integer> requestCount = new ConcurrentHashMap<>(); private static final int LIMIT = 10; public static boolean allowRequest(String ip) { //原子性地增加计数 Integer count = requestCount.merge(ip, 1, Integer::sum); return count <= LIMIT; } // 每秒清空一次(由定时任务调用) public static void reset() { requestCount.clear(); } }
merge(key, 1, Integer::sum)
:
如果 key 不存在,插入
1
如果存在,执行
sum
函数(oldValue + 1
)
原子操作,线程安全
7. 案例 4:状态机管理
场景:订单状态流转,每个订单一个状态。
public class OrderStatusManager { // key: 订单ID, value: 状态 private static final ConcurrentHashMap<String, String> orderStatus = new ConcurrentHashMap<>(); public static boolean changeStatus(String orderId, String expectedStatus, String newStatus) { // CAS 风格更新 return orderStatus.replace(orderId, expectedStatus, newStatus); } // 初始化订单 public static void createOrder(String orderId) { orderStatus.putIfAbsent(orderId, "CREATED"); } public static String getStatus(String orderId) { return orderStatus.get(orderId); } }
putIfAbsent
:如果不存在才插入,避免重复创建replace(key, expect, update)
:只有当前值等于expect
才更新,类似compareAndSet
适合实现乐观锁式的状态变更。
8. 案例 5:连接池或资源池
场景:管理数据库连接,避免重复创建。
public class ConnectionPool { private static final ConcurrentHashMap<String, Connection> pool = new ConcurrentHashMap<>(); private static final String DEFAULT_URL = "jdbc:mysql://localhost:3306/test"; public static Connection getConnection() { return pool.computeIfAbsent(DEFAULT_URL, url -> { System.out.println("创建新连接..."); return createConnection(url); }); } private static Connection createConnection(String url) { // 模拟创建连接 return new Connection(url); } public static void closeConnection(String url) { Connection conn = pool.remove(url); if (conn != null) { conn.close(); } } }
computeIfAbsent
:原子操作,确保只创建一次连接多个线程同时调用
getConnection()
,也只会创建一个连接
9. 常用方法速查表
方法 | 用途 | 是否原子 |
| 获取值 | ✅ |
| 设置值 | ✅ |
| 不存在才插入 | ✅ |
| 删除 | ✅ |
| CAS 风格更新 | ✅ |
| 计算并更新 | ✅ |
| 合并值 | ✅ |
| 不存在时计算 | ✅ |
| 存在时计算 | ✅ |
| 获取大小(估算值) | ✅ |
注意:size()
在高并发下是估算值,因为它是遍历所有段累加的,过程中可能有变化。
10. 注意事项
优先使用
computeIfAbsent
:避免get + put
的竞态条件避免使用
size()
做精确判断:如“如果 size < 100 才添加”,可能不准迭代时注意:
ConcurrentHashMap
支持遍历中修改,但不保证反映最新的修改不要用
ConcurrentHashMap
实现复杂逻辑:如果逻辑复杂,考虑用synchronized
或ReentrantLock
11. 总结
场景 | 推荐方法 |
缓存读取 |
|
计数统计 |
|
限流控制 |
|
状态变更 |
|
资源池 |
|
0条评论
点击登录参与评论