在這個簡短的博客系列的最后BadTransferOperation
中,我一直在討論分析死鎖,我將修復BadTransferOperation
代碼。 如果您看過本系列的其他博客 ,那么您將知道,為了達到這一點,我創建了死鎖的演示代碼,展示了如何掌握線程轉儲,然后分析了線程轉儲,弄清楚發生僵局的位置和方式。 為了節省空間,下面的討論同時引用了本系列第1部分中的Account
和DeadlockDemo
類,其中包含完整的代碼清單。
教科書中有關死鎖的描述通常是這樣的:“線程A將獲得對象1的鎖定,并等待對象2的鎖定,而線程B將獲得對象2的鎖定,同時等待對象1的鎖定”。 我以前的博客中顯示的堆積,并在下面突出顯示,是一個真實的死鎖,其他線程,鎖和對象陷入了直接,簡單,理論上的死鎖情況。
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
對象( fromAccount
),正在等待鎖定其toAccount
對象(e98)
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的鎖。 這個現實世界的僵局只是教科書描述的一個更復雜的版本。
這段代碼的問題是,從下面的代碼片段中,您可以看到我正在從Accounts
數組中隨機選擇兩個Account
對象作為fromAccount
和toAccount
并將它們鎖定。 由于fromAccount
和toAccount
可以引用accounts數組中的任何對象,這意味著它們以隨機順序被鎖定。
Account toAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));Account fromAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));
因此, 解決方法是對Account
對象的鎖定方式施加順序,并且只要順序一致,任何順序都可以執行。
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
對象,以便永遠不會出現以上的死鎖情況。
以下代碼是此修復程序的完整列表:
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;}@Overridepublic 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上找到,網址為git://github.com/roghughe/captaindebug.git
參考: 調查死鎖-第4部分:來自Captain Debug博客博客的JCG合作伙伴 Roger Hughes 修復代碼 。
翻譯自: https://www.javacodegeeks.com/2012/11/investigating-deadlocks-part-4-fixing-the-code.html