本帖最后由 朱晓杰 于 2013-5-7 23:26 编辑
一、理解死锁
经典的"哲学家进餐"的问题很好的描述了死锁状况。
5个哲学家去吃中餐,坐在一张圆桌旁。他们有5根筷子(而不是5双),并且每两个人之间放一根筷子,
哲学家们时而思考,时而进餐。每个人都需一双筷子才能吃到东西,并在吃完后将筷子放到原处继续思考。
如果每个人都立即抓住自己左边的筷子,然后等待右边的筷子空出来,但同时又不放下已经拿到的筷子。
这种情况将产生死锁:每个人都拥有其他人需要的资源,同时又等待其他人已经拥有的资源,并且每个人
在获得所有需要的资源之前都不会放弃已经拥有的资源。
死锁(DeadLock),是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。
产生死锁的四个必要条件是:
1:互斥条件:资源每次只能被一个线程使用。如前面的“线程同步代码段”。
2:请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
3:不剥夺条件:进程已获得的资源,在未使用完之前,无法强行剥夺。
4:循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
二、死锁示例
1.最简单的死锁形式
在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L那么这两个线程将永远的等待下去- public class DeadLockDemo {
- public static void main(String[] args) {
- DeadLock d = new DeadLock();
- Thread a = new Thread(d);
- Thread b = new Thread(d);
- a.start();
- b.start();
- }
- }
- class DeadLock implements Runnable{
- private boolean flag = true;//控制线程的操作交替进行
- private final Object l = new Object();
- private final Object m = new Object();//定义两个静态锁
-
- public void run(){
- while(true){
- if(flag){
- synchronized(l){//①
- System.out.println("...if l...");
- synchronized(m){//②
- System.out.println("...if m...");
- }
- }
- flag = false;
- }else{
- synchronized(m){//③
- System.out.println("...else m...");
- synchronized(l){//④
- System.out.println("...else l...");
- }
- }
- flag = true;
- }
- }
- }
- }
复制代码 发生死锁的原因:两个线程试图以不同的顺序来获得相同的锁。
分析:
线程a执行到①,锁定了M,此时线程b抢占了CPU,获得了执行权,锁定了L,而线程a对M的锁定还没有解除,导致线程b无法运行下去,同样由于线程b锁定了L,线程a也无法运行。
2.银行转账死锁示例- /*
- * 需求:简单的银行转账,它将资金从一个账户转到另一个账户
- * 在开始转账之前,需要获得两个Account的锁,以确保以原子的方式更新账户中的余额,且不能破坏不可变的条件,如账户的余额不能为负数
- */
- /*账户类*/
- class Account{
- private String accountName;//账号
- private int balance;//资金总额
- public Account(String accountName,int balance){
- this.accountName = accountName;
- this.balance = balance;
- }
- /**/
- public String getAccountName() {//获取账号
- return accountName;
- }
- public int getBalance() {//获取账号余额
- return balance;
- }
-
- public void debit(int amount){//更新转出方余额
- this.balance -= amount;
- }
-
- public void credbit(int amount){//更新转入方余额
- this.balance += amount;
- }
- }
- class TransferAccount implements Runnable{
- public Account fromAccount;//转出账户
- public Account toAccount;//转入账户
- public int amount;//转出金额
- public TransferAccount(Account fromAccount,Account toAccount,int amount){
- this.fromAccount = fromAccount;
- this.toAccount = toAccount;
- this.amount = amount;
- }
- public void run(){
- while(true){
- synchronized(fromAccount){
- synchronized(toAccount){
- if(fromAccount.getBalance() <= 0){//转账进行的条件:判断转出账户的余额是否大于0
- System.out.println(fromAccount.getAccountName() + "账户余额不足,无法进行转账");
- return;
- }else{
- fromAccount.debit(amount);//更新转出账户的余额:-
- toAccount.credbit(amount);//更新转入账户的余额:+
- }
- }
- }
- System.out.println(fromAccount.getAccountName() + "......" + fromAccount.getBalance());//打印转出账户的余额
- System.out.println(toAccount.getAccountName() + "---" + toAccount.getBalance());//打印转入账户的余额
- }
- }
- }
- public class BankTransfer {
- public static void main(String[] args) {
- Account fromAccount = new Account("张三",100000);
- Account toAccount = new Account("李四",200000);
-
- Thread a = new Thread(new TransferAccount(fromAccount,toAccount,1));
- Thread b = new Thread(new TransferAccount(toAccount,fromAccount,2));
-
- a.start();
- b.start();
- }
- }
复制代码 发生死锁的原因:两个线程试图以不同的顺序来获得相同的锁
分析:所有的线程并不是按照相同的顺序来获取锁,锁的顺序取决于传递的参数顺序,而这些参数顺序又取决于外部输入,如果线程a和线程b同时转账,线程a从张三向李四转账,线程b从李四向张三转账,那么就会发生死锁:
a:new TransferAccount(fromAccount,toAccount,1)
b:new TransferAccount(toAccount,fromAccount,2)
如果执行顺序不恰当,那么a可能获得fromAccount的锁并且等待toAccount的锁,然而此时b拥有toAccount的锁,并正在等待fromAccount的锁。
|