位置:首頁 > Java技術 > Java教學 > Java線程死鎖

Java線程死鎖

一種特殊類型的錯誤,需要避免對多任務具體涉及死鎖,當兩個線程有一個循環依賴於一對同步對象時發生。

例如,假設一個線程進入監視器對象X和其他線程進入監視器對象Y。如果在X中的線程試圖調用Y上的任何synchronized方法,它會阻止預期。但是,如果在Y中的線程,反過來,嘗試調用X上的任何synchronized方法,該線程將永遠等待,因為訪問的X,那就要釋放自己的Y上的鎖,使第一個線程可以完成。

例子:

要充分認識死鎖看到它的實際是有用的。下一個示例創建兩個類,A和B,具有方法foo()和bar(),分彆為,其試圖在其他類調用一個方法之前稍作停頓。

主類,命名為死鎖,創建一個A和一個B實例,然後啟動第二個線程來設置死鎖條件。foo()和bar()方法使用sleep()方法,以此來迫使死鎖情況發生。

class A {
   synchronized void foo(B b) {
      String name = Thread.currentThread().getName();
      System.out.println(name + " entered A.foo"); 
      try {
         Thread.sleep(1000);
      } catch(Exception e) {
         System.out.println("A Interrupted");
      }
      System.out.println(name + " trying to call B.last()");
      b.last();
   }
   synchronized void last() {
      System.out.println("Inside A.last");
   }
}
class B {
   synchronized void bar(A a) {
      String name = Thread.currentThread().getName();
      System.out.println(name + " entered B.bar");
      try {
         Thread.sleep(1000);
      } catch(Exception e) {
         System.out.println("B Interrupted");
      }
      System.out.println(name + " trying to call A.last()");
      a.last();
   }
   synchronized void last() {
      System.out.println("Inside A.last");
   }
}
public class Deadlock implements Runnable {
   A a = new A();
   B b = new B();
   Deadlock() {
      Thread.currentThread().setName("MainThread");
      Thread t = new Thread(this, "RacingThread");
      t.start();
      a.foo(b); // get lock on a in this thread.
      System.out.println("Back in main thread");
   }
   public void run() {
      b.bar(a); // get lock on b in other thread.
      System.out.println("Back in other thread");
   }
   public static void main(String args[]) {
      new Deadlock();
   }
}

下麵是該程序的一些輸出:

MainThread entered A.foo
RacingThread entered B.bar
MainThread trying to call B.last()
RacingThread trying to call A.last()

因為程序已經死鎖時需要按Ctrl-C來結束程序。可以看到一個完整的線程,並通過按PC上的CTRL-BREAK監視緩存轉儲。

會看到RacingThread持有B上監視器,而它正在等待a監視器上。與此同時,MainThread擁有並正在等待獲取灣這個程序將永遠不會完成。

這個例子說明,如果多線程程序鎖定偶然,死鎖應該檢查的首要條件之一。

順序鎖:

一個常見的​​線程伎倆來避免死鎖是順序鎖。通過順序鎖,它給線程特定順序獲得多個鎖。

死鎖例子:

下麵是一個死鎖的描述:

// File Name ThreadSafeBankAccount.java
public class ThreadSafeBankAccount
{
   private double balance;
   private int number;
   public ThreadSafeBankAccount(int num, double initialBalance)
   {
      balance = initialBalance;
      number = num;
   }
   public int getNumber()
   {
      return number;
   }
   public double getBalance()
   {
      return balance;
   }
   public void deposit(double amount)
   {
      synchronized(this)
      {
        double prevBalance = balance;
        try
        {
           Thread.sleep(4000);
        }catch(InterruptedException e)
        {}
        balance = prevBalance + amount;
      }
   }
   public void withdraw(double amount)
   {
      synchronized(this)
      {
	     double prevBalance = balance;
         try
         {
            Thread.sleep(4000);
         }catch(InterruptedException e)
         {}
         balance = prevBalance - amount;
      }
   }
}

// File Name LazyTeller.java
public class LazyTeller extends Thread
{
   private ThreadSafeBankAccount source, dest;
   public LazyTeller(ThreadSafeBankAccount a, 
                     ThreadSafeBankAccount b)
   {
      source = a;
      dest = b;
   }
   public void run()
   {
      transfer(250.00);
   }
   public void transfer(double amount)
   {
      System.out.println("Transferring from "
          + source.getNumber() + " to " + dest.getNumber());
      synchronized(source)
      {
          Thread.yield();
          synchronized(dest)
          {
             System.out.println("Withdrawing from "
                     + source.getNumber());
             source.withdraw(amount);
             System.out.println("Depositing into "
                     + dest.getNumber());
             dest.deposit(amount);
          }
       }
   }
}
public class DeadlockDemo
{
   public static void main(String [] args)
   {
      System.out.println("Creating two bank accounts...");
      ThreadSafeBankAccount checking =
                    new ThreadSafeBankAccount(101, 1000.00);
      ThreadSafeBankAccount savings =
                    new ThreadSafeBankAccount(102, 5000.00);

      System.out.println("Creating two teller threads...");
      Thread teller1 = new LazyTeller(checking, savings);
      Thread teller2 = new LazyTeller(savings, checking);
      System.out.println("Starting both threads...");
      teller1.start();
      teller2.start();
   }
}

這將產生以下結果:

Creating two bank accounts...
Creating two teller threads...
Starting both threads...
Transferring from 101 to 102
Transferring from 102 to 101

LazyTeller類的問題是,它並冇有考慮競爭條件,經常發生在多線程編程的可能性。

兩個線程啟動後,teller1抓住檢查鎖和teller2抓住儲蓄鎖。當teller1嘗試獲取鎖儲蓄,它是不可用。因此,teller1阻塞,直到儲蓄鎖變為可用。當teller1線程塊,teller1仍然有檢查鎖,不讓他走。

同樣,teller2正在等待檢查鎖,所以teller2塊不放積蓄鎖定。這導致了一個結果:死鎖!

死鎖解決方案示例:

在這裡,transfer() 方法,在一個名為OrderedTeller類,在任意同步上的鎖代替,這種transfer() 方法的基礎上,銀行帳戶號碼指定順序獲得鎖。

// File Name ThreadSafeBankAccount.java
public class ThreadSafeBankAccount
{
   private double balance;
   private int number;
   public ThreadSafeBankAccount(int num, double initialBalance)
   {
      balance = initialBalance;
      number = num;
   }
   public int getNumber()
   {
      return number;
   }
   public double getBalance()
   {
      return balance;
   }
   public void deposit(double amount)
   {
      synchronized(this)
      {
        double prevBalance = balance;
        try
        {
           Thread.sleep(4000);
        }catch(InterruptedException e)
        {}
        balance = prevBalance + amount;
      }
   }
   public void withdraw(double amount)
   {
      synchronized(this)
      {
	     double prevBalance = balance;
         try
         {
            Thread.sleep(4000);
         }catch(InterruptedException e)
         {}
         balance = prevBalance - amount;
      }
   }
}
// File Name OrderedTeller.java
public class OrderedTeller extends Thread
{
   private ThreadSafeBankAccount source, dest;
   public OrderedTeller(ThreadSafeBankAccount a,
                        ThreadSafeBankAccount b)
   {
      source = a;
      dest = b;
   }
   public void run()
   {
      transfer(250.00);
   }
   public void transfer(double amount)
   {
       System.out.println("Transferring from " + source.getNumber()
           + " to " + dest.getNumber());
       ThreadSafeBankAccount first, second;
       if(source.getNumber() < dest.getNumber())
       {
          first = source;
          second = dest;
       }
       else
       {
          first = dest; 
          second = source;
       }
       synchronized(first)
       {
          Thread.yield();
          synchronized(second)
          {
             System.out.println("Withdrawing from "
                         + source.getNumber());
             source.withdraw(amount);
             System.out.println("Depositing into "
                         + dest.getNumber());
             dest.deposit(amount);
          }
      }
   }
}

// File Name DeadlockDemo.java
public class DeadlockDemo
{
   public static void main(String [] args)
   {
      System.out.println("Creating two bank accounts...");
      ThreadSafeBankAccount checking =
                    new ThreadSafeBankAccount(101, 1000.00);
      ThreadSafeBankAccount savings =
                    new ThreadSafeBankAccount(102, 5000.00);

      System.out.println("Creating two teller threads...");
      Thread teller1 = new OrderedTeller(checking, savings);
      Thread teller2 = new OrderedTeller(savings, checking);
      System.out.println("Starting both threads...");
      teller1.start();
      teller2.start();
   }
}

這將消除死鎖問題,並會產生以下結果:

Creating two bank accounts...
Creating two teller threads...
Starting both threads...
Transferring from 101 to 102
Transferring from 102 to 101
Withdrawing from 101
Depositing into 102
Withdrawing from 102
Depositing into 101