無需贅述,線程可以處于多種狀態之一,如下面的UML狀態圖所示……

…死鎖與BLOCKED狀態有關,API文檔將其定義為“一個等待監視器鎖定而被阻塞的線程”。
那么,什么是僵局? 簡而言之,在給定兩個線程A和B的情況下,當線程A由于等待線程B釋放監視器鎖定而阻塞時,而線程B因等待線程A釋放相同的監視器鎖定而阻塞而發生死鎖。
但是,事情可能比這更復雜,因為死鎖可以包含一堆線程。 例如,線程A因為正在等待線程B而阻塞,線程B因為正在等待線程C而阻塞,線程C因為正在等待線程D而阻塞,所以線程D因為正在等待E,E而阻塞,因為它正在等待F和F阻塞,因為它正在等待A。
訣竅是找出哪些線程被阻塞以及為什么被阻塞,這是通過從應用程序中獲取線程轉儲來完成的。 線程轉儲只是快照報告,顯示給定時間點所有應用程序線程的狀態。 有幾種工具和技術可以幫助您掌握線程轉儲,其中包括jVisualVM , jstack和unix kill
命令。 但是,在獲取和解釋線程轉儲之前,我需要一些代碼來創建死鎖
我為此選擇的方案是簡單的銀行帳戶轉帳之一。 這個想法是,有一個余額轉移程序正在運行,該程序使用一堆線程在不同帳戶之間隨機轉移各種金額。 在此程序中,使用以下非常簡單的Account
類來表示銀行帳戶:
public class Account {private final int number;private int balance;public Account(int number, int openingBalance) {this.number = number;this.balance = openingBalance;}public void withdraw(int amount) throws OverdrawnException {if (amount > balance) {throw new OverdrawnException();}balance -= amount;}public void deposit(int amount) {balance += amount;}public int getNumber() {return number;}public int getBalance() {return balance;}
}
上面的類對具有帳戶號和余額屬性以及諸如deposit(...)
和withdraw(...)
類的操作的銀行帳戶進行建模。 如果要提取的金額大于可用余額,則withdraw(...)
將引發一個簡單的檢查異常OverdrawnException
。
示例代碼中其余的類是DeadlockDemo
及其嵌套類BadTransferOperation
。
public class DeadlockDemo {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[]) {DeadlockDemo demo = new DeadlockDemo();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);}}// This will never get to here...System.out.println("Thread Complete: " + threadNum);}private void printNewLine(int columnNumber) {if (columnNumber % MAX_COLUMNS == 0) {System.out.print("\n");}}/*** The clue to spotting deadlocks is in the nested locking - synchronized keywords. Note that the locks DON'T* have to be next to each other to be nested.*/private void transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {synchronized (fromAccount) {synchronized (toAccount) {fromAccount.withdraw(transferAmount);toAccount.deposit(transferAmount);}}}}
}
DeadlockDemo
提供了創建DeadlockDemo
的應用程序框架。 它有兩個簡單的任務: setup()
和run()
。 setup()
創建10個帳戶,并使用一個帳號和一個隨機的期初余額對其進行初始化。 run()
創建嵌套類BadTransferOperation
20個實例,該實例僅擴展Thread
并使它們開始運行。 請注意,用于線程數和帳戶數的值完全是任意的。
BadTransferOperation
是所有動作發生的地方。 它的run()
方法循環執行10000次,從accounts
列表中隨機選擇兩個帳戶,并將0到1000之間的隨機數從一個accounts
轉移到另一個accounts
。 如果fromAccount
中的資金不足,則會引發異常,并在屏幕上顯示“-”。 如果一切順利,并且傳輸成功,則為“。”。 在屏幕上打印。
問題的核心是包含FAULTY同步代碼的方法transfer(Account fromAccount, Account toAccount, int transferAmount)
:
synchronized (fromAccount) {synchronized (toAccount) {fromAccount.withdraw(transferAmount);toAccount.deposit(transferAmount);}}
此代碼首先鎖定fromAccount
,然后toAccount
轉移現金,隨后釋放這兩個鎖定前。
給定兩個線程A和B以及帳戶1和2,則線程A鎖定其編號為1的fromAccount
并嘗試將其鎖定為帳戶2的toAccount
,將出現問題。同時,線程B鎖定其編號2和2的fromAccount
。嘗試鎖定其toAccount
,即帳戶號1。因此,線程A在線程B上被toAccount
,線程B在線程A上被阻塞–死鎖。
如果運行此應用程序,則將獲得一些類似于以下內容的輸出:

…隨著程序突然停止。
現在,我有一個死鎖的應用程序,我的下一個博客實際上將掌握線程轉儲,并了解它的全部含義。
參考: Captain Debug's Blog博客中的調查死鎖-第1部分,來自我們的JCG合作伙伴 Roger Hughes。
翻譯自: https://www.javacodegeeks.com/2012/10/investigating-deadlocks-part-1.html