Java同步方法围绕参数值的实现

5
考虑以下方法:
public void upsert(int customerId, int somethingElse) {
  // some code which is prone to race conditions    
}

我希望保护这个方法,防止出现竞态条件,但只有在两个具有相同customerId的线程同时调用它时才会发生。如果我使整个方法同步,这将降低效率并且实际上并不需要。我真正想要的是围绕customerId进行同步。在Java中是否可能实现这一点?是否有任何内置工具可以实现这一点,或者我需要使用Map作为锁的Integer?
另外,如果您认为我在做错什么,请随时提供建议:)
谢谢!

1
这肯定感觉不对。也许你应该编辑你的问题,提供更多关于为什么你需要同步的细节。 - Sergei Tachenov
3
upsert听起来像是执行一些数据库DML操作。如果是这种情况,也许你正在试图通过锁定来解决事务隔离问题?如果是这样的话,那将是一个糟糕的想法。 - Nathan Hughes
@NathanHughes 我正在使用一个旧版本的Postgres,其中尚未提供upsert :) - Anton Belev
2
也许我没有理解到重点,但是为什么不在JDBC事务中完成,并让DBMS为您处理锁定呢? - Klitos Kyriacou
2个回答

13

你要寻找的概念叫做分段锁定 或者 条纹锁定。为每个客户端单独设置锁定是非常浪费的(锁定很重)。相反,你应该将客户ID空间进行分区到合理数量的分区,以匹配所需的并行度。通常8-16个足够了,但这取决于该方法执行的任务量。

以下是一个简单的方法:

private final Object[] locks = new Object[8];

synchronized (locks[customerId % locks.length]) {
    ...implementation...
}

Google Guava提供了特殊的锁来支持分段锁定 - com.google.common.util.concurrent.Striped - foal

0
    private static final Set<Integer> lockedIds = new HashSet<>();

    private void lock(Integer id) throws InterruptedException {
        synchronized (lockedIds) {
            while (!lockedIds.add(id)) {
                lockedIds.wait();
            }
        }
    }

    private void unlock(Integer id) {
        synchronized (lockedIds) {
            lockedIds.remove(id);
            lockedIds.notifyAll();
        }
    }

    public void upsert(int customerId) throws InterruptedException {
        try {
            lock(customerId);

            //Put your code here.
            //For different ids it is executed in parallel.
            //For equal ids it is executed synchronously.

        } finally {
            unlock(customerId);
        }
    }
  • id 可以是任何一个正确重写了 'equals' 和 'hashCode' 方法的类,而不仅仅是 'Integer'。
  • try-finally - 非常重要 - 即使您的操作抛出异常,您也必须保证在操作后解锁等待线程。
  • 如果您的后端分布在多个服务器/JVMs上,则无法正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接