TL;DR:
I use 并发引用哈希映射 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/ConcurrentReferenceHashMap.html来自 Spring 框架。请检查下面的代码。
虽然这个帖子很老了,但仍然很有趣。因此,我想分享一下我使用Spring框架的方法。
我们正在尝试实现的称为命名互斥体/锁。正如建议的都铎的回答 https://stackoverflow.com/a/12450424/1814420,这个想法是有一个Map
存储锁名称和锁对象。代码如下所示(我从他的答案中复制它):
Map<String, Object> locks = new HashMap<String, Object>();
locks.put("a", new Object());
locks.put("b", new Object());
然而,这种方法有两个缺点:
- OP已经指出了第一个:如何同步访问
locks
哈希映射?
- 如何删除一些不再需要的锁?否则,
locks
哈希图将不断增长。
第一个问题可以通过使用来解决并发哈希映射 https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html。对于第二个问题,我们有两个选择:手动检查并从映射中删除锁,或者以某种方式让垃圾收集器知道哪些锁不再使用,GC 将删除它们。我会选择第二种方式。
当我们使用HashMap
, or ConcurrentHashMap
,它创建了强引用。要实现上面讨论的解决方案,应该使用弱引用(要了解什么是强/弱引用,请参阅本文 https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references or 这个帖子 https://stackoverflow.com/questions/9809074/java-difference-between-strong-soft-weak-phantom-reference).
所以,我用并发引用哈希映射 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/ConcurrentReferenceHashMap.html来自 Spring 框架。如文档中所述:
A 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);
}
}
Usage:
@Autowired
private MutexFactory<String> mutexFactory;
public void doSomething(String name){
synchronized(mutexFactory.getMutex(name)) {
// ...
}
}
单元测试(该测试使用等待性 https://github.com/awaitility/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);
}
}