Блокировка нескольких объектов - PullRequest
0 голосов
/ 10 января 2020

Как получить блокировку нескольких предметов?

Рассмотрим пример ниже:

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
}

Как мне go решить эту проблему? Мне нужно поддерживать блокировки только для двух объектов аккаунта.

Также возможен дубликат (но я хотел знать, есть ли другие решения). Предотвращение тупиковой ситуации в фиктивной банковской базе данных

Ответы [ 2 ]

2 голосов
/ 10 января 2020

Невозможно получить одну блокировку для нескольких объектов. Но есть альтернативы.

  1. Вы можете использовать общую (иначе называемую «глобальную») блокировку. Проблема в том, что эта блокировка может быть узким местом.

  2. Вы можете использовать какой-то другой объект в качестве прокси для двух объектов и получить блокировку для этого. Например, если предположить, что ваши учетные записи имеют уникальные идентификаторы:

    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". Для решения этой проблемы вы можете использовать случайно сгенерированные значения времени ожидания.

(Примечание: не пытайтесь использовать идентификационный хеш-код в качестве прокси для уникального идентификатора. Идентификационные хеш-коды не являются уникальный, и у вас также есть проблема, что у вас может быть несколько Account объектов в памяти, которые представляют одну и ту же логическую учетную запись ... если они являются DTO.)

0 голосов
/ 10 января 2020

Вы можете заблокировать два объекта и все же избежать тупика, используя порядок блокировки

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

Таким образом, мы избегаем циклического ожидания.

Я предполагаю, что ваши записи Hashmap поточно-ориентированы или это только для чтения.

...