2025-10-10 17:01

ConcurrentHashMap使用详解

王姐姐

Java后端

(38)

(0)

收藏

1. 什么是 ConcurrentHashMap

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

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

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

2. 为什么不用 HashMapsynchronizedMap

方案

问题

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 实现复杂逻辑:如果逻辑复杂,考虑用 synchronizedReentrantLock

11. 总结

场景

推荐方法

缓存读取

get() / putIfAbsent()

计数统计

compute() / merge() / LongAdder

限流控制

merge()

状态变更

replace() / compute()

资源池

computeIfAbsent()


0条评论

点击登录参与评论