#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. Есть и другие фрагменты кода, которые требуют обеих блокировок, а не только один пример, который я опубликовал. Я поясню это.