В жизни обычного программиста редко возникает возможность писать что-то действительно крутое и интересное. Большая часть нашей работы связана лишь со скрупулёзностью, усидчивостью, вниманием и монотонностью. Лишь на собеседованиях можно применить что-либо действительно интересное. Или странное. В этой статье я опишу способ решения проблемы смертельных блокировок (deadlock-ов), который может вам пригодиться при прохождении некоторых собеседований.Предположим, что у нас есть система, работающая со счетами пользователей. Счета пользователей представлены классом Account:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class Account { private final int id; private int amount; public Account(int id) { this.id = id; } public int getId() { return this.id; } public int getAmount() { return this.amount; } public synchronized void transfer(Account fromAccount, int transferSum) { synchronized (fromAccount) { fromAccount.amount -= transferSum; this.amount += transferSum; } } } |
На первый взгляд может показаться, что всё в порядке. Но на самом деле в этом коде спрятана взаимная блокировка. Если вызвать transfer на первом счёте, а затем на втором, то может произойти так, что:
- Берётся блокировка this на методе synchronized у счёта 1.
- Происходит переключение потоков.
- Берётся блокировка this на методе synchronized у счёта 2.
- Происходит переключение потоков.
- Поток, уже имеющий блокировку на счёте 1, пытается взять блокировку на счёте 2 в синхронизированном блоке, но блокировка this у счёта 2 уже занята, поэтому поток останавливается до тех пор, пока блокировка счёта 2 не освободиться.
- Поток, уже имеющий блокировку на счёте 2, пытается взять блокировку на счёте 1 в синхронизированном блоке, но блокировка this у счёта 1 уже занята, поэтому поток останавливается до тех пор, пока блокировка счёта 1 не освободится.
- Оба потока ждут освобождения блокировок друг друга, чего никогда не произойдёт.
Как избавиться от подобного? В данном случае можно обратить внимание на идентификатор счёта id. Понятное дело, что каждый счёт имеет уникальный идентификатор. Чтобы избежать deadlock-ов мы можем всегда брать блокировки строго в порядке сортировки их идентификаторов, то есть сначала меньший, а затем больший, чем решим проблему взаимных блокировок для этого случая:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class Account { private final int id; private int amount; public Account(int id) { this.id = id; } public int getId() { return this.id; } public int getAmount() { return this.amount; } public void transfer(Account fromAccount, int transferSum) { Account firstBlock; Account secondBlock; if (this.id < fromAccount.id) { firstBlock = this; secondBlock = fromAccount; } else { firstBlock = fromAccount; secondBlock = this; } synchronized (firstBlock) { synchronized (secondBlock) { fromAccount.amount -= transferSum; this.amount += transferSum; } } } } |
Если же окажется так, что объекты на которых нужно взять блокировку, не имеют уникальных полей, по которым можно определять меньший и больший объект, то можно ввести подобные поля синтетически.
.Предположим -> После точки нет пробела
Чтобы избежать deadlock-ов мы можем (Запятая?)
что объекты на которых нужно взять блокировку -> запятая после «объекты»