基于参数(名为互斥锁/锁)的 Java 同步

2022-09-01 05:57:23

我正在寻找一种方法来根据它收到的参数同步方法,如下所示:

public synchronized void doSomething(name){
//some code
}

我希望根据参数同步该方法,如下所示:doSomethingname

线程 1: doSomething(“a”);

线程 2: doSomething(“b”);

线程 3: doSomething(“c”);

线程 4: doSomething(“a”);

线程 1、线程 2 和线程 3 将在不同步的情况下执行代码,但线程 4 将等到线程 1 完成代码,因为它具有相同的“a”值。

谢谢

更新

根据都铎的解释,我认为我面临着另一个问题:这是新代码的示例:

private HashMap locks=new HashMap();
public void doSomething(String name){
    locks.put(name,new Object());
    synchronized(locks.get(name)) {
        // ...
    }
    locks.remove(name);
}

我不填充锁映射的原因是因为名称可以具有任何值。

根据上面的示例,当多个线程同时从哈希映射中添加/删除值时,可能会出现问题,因为HashMap不是线程安全的。

所以我的问题是,如果我使a是线程安全的,同步块会阻止其他线程访问locks.get(name)吗?HashMapConcurrentHashMap


答案 1

使用映射将字符串与锁定对象相关联:

Map<String, Object> locks = new HashMap<String, Object>();
locks.put("a", new Object());
locks.put("b", new Object());
// etc.

然后:

public void doSomething(String name){
    synchronized(locks.get(name)) {
        // ...
    }
}

答案 2

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个缺点:

  1. OP已经指出了第一个问题:如何同步对哈希映射的访问?locks
  2. 如何移除一些不再需要的锁?否则,哈希映射将继续增长。locks

第一个问题可以通过使用 ConcurrentHashMap 来解决。对于第二个问题,我们有2个选项:手动检查并从地图中删除锁,或者以某种方式让垃圾回收器知道哪些锁不再使用,GC将删除它们。我将采用第二种方式。

当我们使用 或 时,它会创建强引用。要实现上面讨论的解决方案,应该改用弱引用(要了解什么是强/弱引用,请参阅本文这篇文章)。HashMapConcurrentHashMap


因此,我使用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);
    }
}