ConcurrentHashMap原子性资源计数控制方案咨询及Semaphore用法验证
ConcurrentHashMap原子性资源计数控制方案咨询及Semaphore用法验证
你好!咱们一步步拆解你的问题,先聊聊最初用ConcurrentHashMap实现时的核心问题,再分析你尝试的Semaphore方案里的错误,最后给出靠谱的实现方式。
一、原ConcurrentHashMap方案的核心问题
你最初写的那段“必须原子”的逻辑,其实根本不是原子操作:
while (map.get(key) + count > 25) { sleep(100); } map.put(key, map.get(key) + count);
map.get(key)和后续的map.put之间存在时间窗口,多个线程同时执行时,会出现多个线程都通过了while检查,然后同时更新计数的情况,最终导致资源计数直接突破25的上限——这完全违背了你控制资源占用的初衷。
而你尝试在merge方法里加循环休眠的方式之所以会挂,是因为ConcurrentHashMap的merge方法在执行你传入的函数时,是持有对应key的分段锁的。你在这个函数里sleep还反复调用map.get(key),相当于把锁一直占着不释放,其他线程根本没法修改这个key的值,最终导致所有线程都被阻塞,程序彻底卡死。
二、你尝试的Semaphore方案的问题
用Semaphore来控制资源访问数的思路是完全正确的,但你的代码里有几个明显的错误:
- 初始许可数不匹配:你原本的资源上限是25,但创建
Semaphore时写的是new Semaphore(5),直接把资源上限改成5了,和需求不符。 - 重复释放许可:你在
try块里已经执行了semaphore.release(count),finally里又写了semaphore.release(intervals.size())——先不说intervals变量没定义,就算是笔误写成count,也会导致重复释放,最终信号量的许可数会超过初始值,彻底失去控制。 - 异常处理缺失:
semaphore.acquire(count)会抛出InterruptedException,你需要要么捕获这个异常,要么在方法上声明抛出。
三、修正后的Semaphore正确实现
基于你的思路,修正后的代码应该是这样的:
// 用ConcurrentHashMap存储每个key对应的信号量,确保线程安全创建与获取 Map<Long, Semaphore> semaphoreMap = new ConcurrentHashMap<>(); // 这里声明抛出InterruptedException,也可以在方法内捕获处理 private void doTask(long key, int count) throws InterruptedException { // 每个key对应一个初始许可为25的信号量,computeIfAbsent确保原子创建 Semaphore semaphore = semaphoreMap.computeIfAbsent(key, k -> new Semaphore(25)); // 尝试获取count个许可,获取不到就自动阻塞(比手动sleep轮询高效得多) semaphore.acquire(count); try { // 执行你的核心计算逻辑 // do some computations } finally { // 无论计算是否成功,都要释放资源,放在finally里确保不会泄漏 semaphore.release(count); } }
这个方案的优势很明显:
- 原子性保障:
computeIfAbsent是原子操作,确保每个key只会创建一个Semaphore实例。 - 高效阻塞:
Semaphore.acquire会在许可不足时自动阻塞,不需要手动轮询休眠,性能更好。 - 资源安全:
finally块里的release确保资源一定会被释放,不会出现资源泄漏的情况。
四、如果一定要用ConcurrentHashMap实现(不推荐)
如果你坚持要用ConcurrentHashMap来做计数,必须用compute方法实现原子的检查+更新逻辑,因为compute方法会在持有锁的情况下执行你的函数,确保整个逻辑是原子的:
Map<Long, Integer> resourceMap = new ConcurrentHashMap<>(); private void doTask(long key, int count) throws InterruptedException { while (true) { // 用compute原子性地检查并更新计数 Integer result = resourceMap.compute(key, (k, current) -> { // current为null说明是第一次访问,初始化为0 int currentCount = current == null ? 0 : current; // 如果加上count不超过25,就更新计数 if (currentCount + count <= 25) { return currentCount + count; } // 否则返回null,表示不更新 return null; }); // 如果result不为null,说明成功获取了资源,退出循环 if (result != null) { break; } // 没获取到就短暂休眠,避免CPU空转 Thread.sleep(100); } try { // 执行计算逻辑 // do some computations } finally { // 原子性地释放资源,用merge或者compute都可以 resourceMap.merge(key, count, (curr, cnt) -> curr - cnt); } }
这个方案虽然能实现需求,但相比Semaphore来说,手动轮询的方式性能更差,代码也更复杂,所以更推荐用Semaphore的方案。
备注:内容来源于stack exchange,提问作者Leonis
相关产品推荐
相关产品推荐

