Как избежать легко создаваемой взаимоблокировки?

#java #locking #deadlock

#java #блокировка #взаимоблокировка

Вопрос:

У меня есть пользователь объекта с двумя блокировками: inventoryLock и currencyLock. Часто эти блокировки будут использоваться по отдельности, например

 synchronized (user.inventoryLock) {

 // swap items
 tmp = user.inventory[x];
 user.inventory[x] = user.inventory[y];
 user.inventory[y] = tmp;

}
  

или

 synchronized (user.currencyLock) {

 if (user.money < loss) throw new Exception();
 user.money -= loss;

}
  

Но иногда для фрагмента кода требуются обе блокировки:

 synchronized (user.currencyLock) {
 synchronized (user.inventoryLock) {

  if (user.money < item.price) throw new Exception();
  user.money -= item.price;
  user.inventory[empty] = item;

 }
}
  

Кажется простым, но есть больше фрагментов кода, использующих обе блокировки, чем только в этом примере, и я знаю из предыдущего опыта, что если для нескольких фрагментов кода требуются одни и те же общие блокировки, у них есть риск взаимоблокировки.

Какой хороший способ избежать этого?

Может быть, есть какой-то механизм, который позволит мне атомарно заблокировать два объекта?

Комментарии:

1. В вашем коде наблюдается серьезный недостаток инкапсуляции. Вы получаете доступ к состоянию пользователя напрямую, не проходя через методы. Состояние должно быть инкапсулировано в классе, что позволило бы централизовать синхронизацию и убедиться, что вся необходимая синхронизация выполнена. Вы заставляете каждого клиента вашего пользователя правильно синхронизироваться, вместо того, чтобы предоставлять эту услугу в самом классе User.

2. @JBNizet Это просто пример кода. Реальный код включает, среди прочего, вызовы базы данных, которые пользовательский класс не способен выполнять.

Ответ №1:

всегда блокируйте одну блокировку перед другой, одним из требований взаимоблокировки является циклический шаблон ожидания

например, если вы можете гарантировать, что вы всегда будете блокировать currencyLock перед блокировкой inventoryLock и никогда не будете пытаться заблокировать currencyLock , когда у вас уже inventoryLock есть, все будет в порядке

Комментарии:

1. Это то, что я тоже придумал, поэтому я не могу не согласиться 🙂

2. Вы не можете не блокировать одно перед другим. Вы всегда должны получать блокировки в том же порядке.

Ответ №2:

Сразу после публикации этого вопроса я сам придумал простое решение: просто убедитесь, что весь код получает блокировки в том же порядке. Таким образом, никогда не может быть двух потоков, содержащих один из них.

Если нет естественного порядка, алфавитного будет достаточно, и его будет легко запомнить.

Ответ №3:

Я не вижу здесь никаких возможностей циклического ожидания, что является 1 из 4 условий, необходимых для взаимоблокировки.

Комментарии:

1. Есть и другие фрагменты кода, которые требуют обеих блокировок, а не только один пример, который я опубликовал. Я поясню это.