锁定多个对象

Lock on multiple objects

如何锁定多个项目?

考虑下面的例子,

Map<String, Account> accountMap = new HashMap<>(); // key=accountNumber,value=AccountObject

class Account{
     private String accountNumber; // getter & setter
     private double accountBalance; // getter & setter
}

我需要将资金从一个账户转移到另一个账户,所以我想有一个嵌套的同步块,但意识到它会导致死锁。

// bad code
synchronized(accountMap.get(accountNumber1)){
    synchronized(accountMap.get(accountNumber2)){
     // business logic
    }
}

此外,我不想要单个锁,因为它会阻止处理一个事务的所有线程。如下所示

//bad code
Object mutex = new Object();

synchronized(mutex){
     // business logic with accountNumber1 & accountNumber2
}

我该如何解决这个问题?我只需要为两个帐户对象维护锁。

此外,可能重复(但我想知道是否有不同的解决方案)。 Preventing deadlock in a mock banking database

您可以锁定两个对象并仍然使用锁定顺序避免死锁

if (accountNumber1 < accountNumber2) {
   synchronized (accountMap.get(accountNumber1)) {
       synchronized (accountMap.get(accountNumber2)) {
           //
       }
   }
} else {
       synchronized (accountMap.get(accountNumber2)) {
           synchronized (accountMap.get(accountNumber1)) {
               //
           }
       }
   }
}

这样我们就避免了循环等待。

我假设你的 Hashmap 写入是线程安全的或者它是只读的。

无法获得多个对象的单个锁。但还有其他选择。

  1. 您可以使用共享(又名 "global")锁。问题是这个锁可能是一个瓶颈。

  2. 您可以使用其他某个对象作为这两个对象的代理,并获得对其的锁定。例如,假设您的帐户具有唯一 ID:

    String lockName = (acc1.getAccountNumber() + "-" 
                       + acc2.getAccountNumber()).intern();
    synchronized (lockName) {
        // we now have a lock on the combination of acc1 and acc2.
    }
    

    (但是,这可能不适用于您的用例,因为它不会停止涉及两个帐户之一和第三个帐户的同时转账。)

  3. 按规范顺序获取锁。例如。

    if (acc1.getAccountNumber().compareTo(acc2.getAccountNumber()) < 0) {
        synchronized (acc1) {
            synchronized (acc2) {
                // do transfer
        }
    } else {
        synchronized (acc2) {
            synchronized (acc1) {
                // do transfer
            }
        }
    }
    

    如果所有线程以相同的顺序获取锁,则不会发生死锁。

  4. 使用 Lock.tryLock 获取锁并加一次。然而,这有几个问题:

    • 您现在需要管理每个帐户 Lock 个对象。
    • 存在"livelock"的(理论)问题。要解决此问题,您可以使用随机生成的超时值。

(注意:不要尝试使用身份哈希码作为唯一 id 的代理。身份哈希码不是唯一的,而且您还会遇到内存中可能有多个的问题 Account表示相同逻辑帐户的对象...如果它们是 DTO。)