В последнем из этой небольшой серии блогов, в которых я говорил об анализе взаимоблокировок, я собираюсь исправить свой код BadTransferOperation
. Если вы видели другие блоги в этой серии , вы будете знать, что для этого я создал демо-код, который блокирует блокировку, показал, как получить дамп потока, а затем проанализировал дамп потока, выяснить, где и как возникла тупиковая ситуация. В целях экономии места в приведенном ниже обсуждении используются классы Account
и DeadlockDemo
из первой части этой серии , в которой содержатся полные списки кодов.
Описание тупиковых ситуаций в учебниках обычно выглядит примерно так: «Поток A получит блокировку на объекте 1 и будет ожидать блокировки на объекте 2, тогда как поток B получит блокировку на объекте 2, ожидая блокировки на объекте 1». Скопление, показанное в моем предыдущем блоге и выделенное ниже, является реальной тупиковой ситуацией, когда другие потоки, блокировки и объекты мешают прямой, простой, теоретической тупиковой ситуации.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
Found one Java-level deadlock: ============================= 'Thread-21' : waiting to lock monitor 7f97118bd560 (object 7f3366f58, a threads.deadlock.Account), which is held by 'Thread-20' 'Thread-20' : waiting to lock monitor 7f97118bc108 (object 7f3366e98, a threads.deadlock.Account), which is held by 'Thread-4' 'Thread-4' : waiting to lock monitor 7f9711834360 (object 7f3366e80, a threads.deadlock.Account), which is held by 'Thread-7' 'Thread-7' : waiting to lock monitor 7f97118b9708 (object 7f3366eb0, a threads.deadlock.Account), which is held by 'Thread-11' 'Thread-11' : waiting to lock monitor 7f97118bd560 (object 7f3366f58, a threads.deadlock.Account), which is held by 'Thread-20' |
Если вы свяжете текст и изображение выше со следующим кодом, вы увидите, что Thread-20
заблокировал свой объект fromAccount (f58) и ожидает блокировки своего объекта toAccount (e98)
1
2
3
4
5
6
7
8
9
|
private void transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException { synchronized (fromAccount) { synchronized (toAccount) { fromAccount.withdraw(transferAmount); toAccount.deposit(transferAmount); } } } |
К сожалению, из-за проблем с синхронизацией, Thread-20
не может получить блокировку для объекта e98, потому что он ожидает, пока Thread-4
освободит свою блокировку для этого объекта. Thread-4
не может снять блокировку, потому что он ожидает Thread-7
, Thread-7
ожидает Thread-11
а Thread-11
ожидает, пока Поток Thread-20
снимет свою блокировку на объекте f58. Этот реальный тупик — просто более сложная версия описания учебника.
Проблема с этим кодом заключается в том, что из приведенного ниже фрагмента видно, что я случайно выбираю два объекта Account
из массива Accounts
в качестве fromAccount
и toAccount
и блокирую их. Поскольку fromAccount
и toAccount
могут ссылаться на любой объект из массива toAccount
, это означает, что они заблокированы в случайном порядке.
1
2
|
Account toAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS)); Account fromAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS)); |
Следовательно, исправление состоит в том, чтобы навести порядок блокирования объекта Account
и выполнения любого ордера, если он согласован.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
private void transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException { if (fromAccount.getNumber() > toAccount.getNumber()) { synchronized (fromAccount) { synchronized (toAccount) { fromAccount.withdraw(transferAmount); toAccount.deposit(transferAmount); } } } else { synchronized (toAccount) { synchronized (fromAccount) { fromAccount.withdraw(transferAmount); toAccount.deposit(transferAmount); } } } } |
Код выше показывает исправление. В этом коде я использую номер Account
записи, чтобы сначала заблокировать объект Account
с наибольшим номером учетной записи, чтобы вышеописанная ситуация взаимоблокировки никогда не возникала.
Код ниже — это полный список исправлений:
01
02
03
04
05
06
07
08
09
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
public class AvoidsDeadlockDemo { private static final int NUM_ACCOUNTS = 10 ; private static final int NUM_THREADS = 20 ; private static final int NUM_ITERATIONS = 100000 ; private static final int MAX_COLUMNS = 60 ; static final Random rnd = new Random(); List<Account> accounts = new ArrayList<Account>(); public static void main(String args[]) { AvoidsDeadlockDemo demo = new AvoidsDeadlockDemo(); demo.setUp(); demo.run(); } void setUp() { for ( int i = 0 ; i < NUM_ACCOUNTS; i++) { Account account = new Account(i, rnd.nextInt( 1000 )); accounts.add(account); } } void run() { for ( int i = 0 ; i < NUM_THREADS; i++) { new BadTransferOperation(i).start(); } } class BadTransferOperation extends Thread { int threadNum; BadTransferOperation( int threadNum) { this .threadNum = threadNum; } @Override public void run() { for ( int i = 0 ; i < NUM_ITERATIONS; i++) { Account toAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS)); Account fromAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS)); int amount = rnd.nextInt( 1000 ); if (!toAccount.equals(fromAccount)) { try { transfer(fromAccount, toAccount, amount); System.out.print( "." ); } catch (OverdrawnException e) { System.out.print( "-" ); } printNewLine(i); } } System.out.println( "Thread Complete: " + threadNum); } private void printNewLine( int columnNumber) { if (columnNumber % MAX_COLUMNS == 0 ) { System.out.print( "\n" ); } } /** * This is the crucial point here. The idea is that to avoid deadlock you need to ensure that threads can't try * to lock the same two accounts in the same order */ private void transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException { if (fromAccount.getNumber() > toAccount.getNumber()) { synchronized (fromAccount) { synchronized (toAccount) { fromAccount.withdraw(transferAmount); toAccount.deposit(transferAmount); } } } else { synchronized (toAccount) { synchronized (fromAccount) { fromAccount.withdraw(transferAmount); toAccount.deposit(transferAmount); } } } } } } |
В моем примере кода возникает тупик из-за проблемы синхронизации и вложенных synchronized
ключевых слов в моем классе BadTransferOperation
. В этом коде synchronized
ключевые слова находятся на соседних строках; однако, в заключение, стоит отметить, что не имеет значения, где в вашем коде synchronized
ключевые слова (они не обязательно должны быть смежными). Пока вы блокируете два (или более) разных объекта монитора одним и тем же потоком, упорядочение имеет место и возникают взаимные блокировки.
Для получения дополнительной информации см. Другие блоги в этой серии .
Весь исходный код для этого и других блогов из этой серии доступен на Github по адресу: gitub.com/roghughe/captaindebug.git.
Ссылка: Исследование тупиков — Часть 4. Исправление кода от нашего партнера по JCG Роджера Хьюза в блоге Captain Debug’s Blog .