TL;DR:
我使用Spring Framework中的ConcurrentReferenceHashMap。请检查下面的代码。
虽然这个线程很旧,但它仍然很有趣。因此,我想与Spring Framework分享我的方法。
我们试图实现的称为名为互斥锁/锁。正如都铎王朝的回答所建议的那样,这个想法是有一个存储锁名称和锁对象。代码将如下所示(我从他的答案中复制它):Map
Map<String, Object> locks = new HashMap<String, Object>();
locks.put("a", new Object());
locks.put("b", new Object());
但是,这种方法有2个缺点:
- OP已经指出了第一个问题:如何同步对哈希映射的访问?
locks
- 如何移除一些不再需要的锁?否则,哈希映射将继续增长。
locks
第一个问题可以通过使用 ConcurrentHashMap 来解决。对于第二个问题,我们有2个选项:手动检查并从地图中删除锁,或者以某种方式让垃圾回收器知道哪些锁不再使用,GC将删除它们。我将采用第二种方式。
当我们使用 或 时,它会创建强引用。要实现上面讨论的解决方案,应该改用弱引用(要了解什么是强/弱引用,请参阅本文或这篇文章)。HashMap
ConcurrentHashMap
因此,我使用Spring Framework中的ConcurrentReferenceHashMap。如文档中所述:
对键和值使用软引用或弱引用的 。ConcurrentHashMap
此类可用作替代方法,以便在并发访问时支持更好的性能。此实现遵循与支持空值和空键相同的设计约束。Collections.synchronizedMap(new WeakHashMap<K, Reference<V>>())
ConcurrentHashMap
这是我的代码。管理所有锁是钥匙的类型。MutexFactory
<K>
@Component
public class MutexFactory<K> {
private ConcurrentReferenceHashMap<K, Object> map;
public MutexFactory() {
this.map = new ConcurrentReferenceHashMap<>();
}
public Object getMutex(K key) {
return this.map.compute(key, (k, v) -> v == null ? new Object() : v);
}
}
用法:
@Autowired
private MutexFactory<String> mutexFactory;
public void doSomething(String name){
synchronized(mutexFactory.getMutex(name)) {
// ...
}
}
单元测试(此测试对某些方法使用 awaitility 库,例如 , 、 :await()
atMost()
until()
public class MutexFactoryTests {
private final int THREAD_COUNT = 16;
@Test
public void singleKeyTest() {
MutexFactory<String> mutexFactory = new MutexFactory<>();
String id = UUID.randomUUID().toString();
final int[] count = {0};
IntStream.range(0, THREAD_COUNT)
.parallel()
.forEach(i -> {
synchronized (mutexFactory.getMutex(id)) {
count[0]++;
}
});
await().atMost(5, TimeUnit.SECONDS)
.until(() -> count[0] == THREAD_COUNT);
Assert.assertEquals(count[0], THREAD_COUNT);
}
}