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