2025-10-10 17:01

ConcurrentHashMap使用详解

王姐姐

Java后端

(863)

(0)

收藏

1. 什么是 ConcurrentHashMap?

ConcurrentHashMap 是 Java 中 线程安全的 HashMap,位于 java.util.concurrent 包下。

它解决了 HashMap 在多线程下不安全、Collections.synchronizedMap() 性能低的问题。

核心优势:高并发读写性能,读操作完全不加锁,写操作只锁部分数据。

2. 为什么不用 HashMap 或 synchronizedMap?

方案

问题

HashMap

多线程下可能死循环(JDK 1.7)、数据错乱

Collections.synchronizedMap(map)

所有操作都加 synchronized,性能差,读写互斥

ConcurrentHashMap

✅ 分段锁 / 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. 常用方法速查表

方法

用途

是否原子

get(key)

获取值

✅

put(key, value)

设置值

✅

putIfAbsent(key, value)

不存在才插入

✅

remove(key)

删除

✅

replace(key, old, new)

CAS 风格更新

✅

compute(key, BiFunction)

计算并更新

✅

merge(key, value, BiFunction)

合并值

✅

computeIfAbsent(key, Function)

不存在时计算

✅

computeIfPresent(key, BiFunction)

存在时计算

✅

size()

获取大小(估算值)

✅

注意:size() 在高并发下是估算值,因为它是遍历所有段累加的,过程中可能有变化。

10. 注意事项

  1. 优先使用 computeIfAbsent:避免 get + put 的竞态条件

  2. 避免使用 size() 做精确判断:如“如果 size < 100 才添加”,可能不准

  3. 迭代时注意:ConcurrentHashMap 支持遍历中修改,但不保证反映最新的修改

  4. 不要用 ConcurrentHashMap 实现复杂逻辑:如果逻辑复杂,考虑用 synchronized 或 ReentrantLock

11. 总结

场景

推荐方法

缓存读取

get() / putIfAbsent()

计数统计

compute() / merge() / LongAdder

限流控制

merge()

状态变更

replace() / compute()

资源池

computeIfAbsent()


0条评论

点击登录参与评论