我经常想要访问(并且可能添加/删除)给定ConcurrentMap
的元素,以便一次只有一个线程可以访问任何单个键。做这个的最好方式是什么?在密钥本身上进行同步不起作用:其他线程可能通过equal
实例访问相同的密钥。
如果答案仅适用于guava MapMaker
制作的地图,那就足够了。
在这里查看一个简单的解决方案Simple Java name based locks?
编辑:这个解决方案有一个明确的发生 - 从解锁到锁定的关系。然而,现在撤回的下一个解决方案却没有。 ConcurrentMap
javadoc太轻了,无法保证。
(已撤消)如果要将地图重新用作锁定池,
private final V LOCK = ...; // a fake value
// if a key is mapped to LOCK, that means the key is locked
ConcurrentMap<K,V> map = ...;
V lock(key)
V value;
while( (value=map.putIfAbsent(key, LOCK))==LOCK )
// another thread locked it before me
wait();
// now putIfAbsent() returns a real value, or null
// and I just sucessfully put LOCK in it
// I am now the lock owner of this key
return value; // for caller to work on
// only the lock owner of the key should call this method
unlock(key, value)
// I put a LOCK on the key to stall others
// now I just need to swap it back with the real value
if(value!=null)
map.put(key, value);
else // map doesn't accept null value
map.remove(key)
notifyAll();
test()
V value = lock(key);
// work on value
// unlock.
// we have a chance to specify a new value here for the next worker
newValue = ...; // null if we want to remove the key from map
unlock(key, newValue); // in finally{}
这是非常混乱的,因为我们重复使用地图有两个不同的目的。将锁池作为单独的数据结构更好,将map简单地保留为k-v存储。
你不能只创建自己的扩展concurrentmap的类。覆盖get(Object key)
方法,以便检查所请求的密钥对象是否已被另一个线程“检出”?
您还必须在您的concurrentmap中创建一个新方法,将项目“返回”到地图,以便它们再次可用于另一个线程。
private static final Set<String> lockedKeys = new HashSet<>();
private void lock(String key) throws InterruptedException {
synchronized (lockedKeys) {
while (!lockedKeys.add(key)) {
lockedKeys.wait();
}
}
}
private void unlock(String key) {
synchronized (lockedKeys) {
lockedKeys.remove(key);
lockedKeys.notifyAll();
}
}
public void doSynchronouslyOnlyForEqualKeys(String key) throws InterruptedException {
try {
lock(key);
//Put your code here.
//For different keys it is executed in parallel.
//For equal keys it is executed synchronously.
} finally {
unlock(key);
}
}