【发布时间】:2017-06-13 09:26:58
【问题描述】:
我有为任意键实现“锁定处理程序”的代码。给定一个key,它确保一次只有一个线程可以process那个(或等于)键(这里意味着调用externalSystem.process(key)调用)。
到目前为止,我有这样的代码:
public class MyHandler {
private final SomeWorkExecutor someWorkExecutor;
private final ConcurrentHashMap<Key, Lock> lockMap = new ConcurrentHashMap<>();
public void handle(Key key) {
// This can lead to OOM as it creates locks without removing them
Lock keyLock = lockMap.computeIfAbsent(
key, (k) -> new ReentrantLock()
);
keyLock.lock();
try {
someWorkExecutor.process(key);
} finally {
keyLock.unlock();
}
}
}
我知道这段代码可以导致OutOfMemoryError,因为没有人清楚地图。
我在考虑如何制作能够累积有限元素数量的地图。当超出限制时,我们应该用新的替换最旧的访问元素(此代码应与最旧的元素同步作为监视器)。但我不知道如何让回调告诉我超出限制。
请分享你的想法。
附言
我重读了任务,现在我发现我有限制,handle 方法不能被调用超过 8 个线程。我不知道它对我有什么帮助,但我只是提到了它。
附言 2
@Boris the Spider 提出了不错且简单的解决方案:
} finally {
lockMap.remove(key);
keyLock.unlock();
}
但是在 Boris 注意到我们的代码不是线程安全的,因为它破坏了行为之后:
让我们研究 3 个使用相同键调用的线程:
- Thread#1 获取锁,现在在
map.remove(key);之前 - Thread#2 使用 equals 键调用,因此它在 thread#1 释放锁定时等待。
- 然后线程#1 执行
map.remove(key);。在这个线程#3 调用方法handle之后。它检查该键的锁是否在映射中不存在,因此它创建新锁并获取它。 - 线程#1 释放锁,因此线程#2 获取它。
因此,线程#2 和线程#3 可以并行调用等于键。但这不应该被允许。
为了避免这种情况,在map清除之前,我们应该阻塞任何线程来获取锁,而waitset中的所有线程都没有获取并释放锁。看起来它需要足够复杂的同步,它会导致算法工作缓慢。当地图大小超过某个限制值时,也许我们应该不时清除地图。
我浪费了很多时间,但不幸的是我不知道如何实现这一点。
【问题讨论】:
-
蜘蛛鲍里斯是这样的吗? } 最后 { lockMap.remove(keyLock); keyLock.unlock(); }
-
@Boris the Spider 但它可以防止 OOM 错误
-
嗯,再想想,这行不通。场景:
1出现,创建一个Lock并锁定它。2出现并找到锁,等待。1完成、解锁和移除。3出现并找不到Lock因此2和3将具有并发访问权限。为那个脑子放屁道歉。 -
@Boris the Spider 嗯...你是对的,但它看起来不错
-
@Boris 这是一个非常好的案例。现在我真的不明白如何从地图中删除价值并确保没有人想获得这个
标签: java concurrency java-8 out-of-memory concurrenthashmap