和风细雨

世上本无难事,心以为难,斯乃真难。苟不存一难之见于心,则运用之术自出。

线程的死锁

本文内容

同步不是改善程序安全性的灵丹妙药。
发生死锁的两种情况和解决方法。

同步不是改善程序安全性的灵丹妙药

从《线程的同步》一节中我们可以知道,synchronized能保证只有一个线程进入同步方法或同步块,但为了安全性盲目给多线程程序加上synchronized关键字并不是问题解决之道,这不但会降低程序的效率;还有可能带来严重的问题-死锁。
死锁发生在两个或以上的线程在等待对象锁被释放,但程序的环境却让lock无法释放时。下面我们将看到两种类型的死锁例子。

某线程不退出同步函数造成的死锁

public class PaintBoard  extends Thread{
  private boolean flag=true;
 
  public void paint(){   
    System.out.println("模拟绘画");
  }
 
  public synchronized void run(){
    while(flag){
      try{
        paint();
        Thread.sleep(1000);
      }
      catch(InterruptedException ex){
        ex.printStackTrace();
      }
    }
  }
 
  public synchronized void stopDraw(){
    flag=false;
    System.out.println("禁止绘画");
  }
 
  public static void main(String[] args){
    PaintBoard paintBoard=new PaintBoard();
    paintBoard.start();
    new StopThread(paintBoard);
  }
}

public class StopThread implements Runnable{
  private PaintBoard paintBoard;
 
  public StopThread(PaintBoard paintBoard){
    this.paintBoard=paintBoard;
   
    Thread th=new Thread(this);
    th.start();
  }
 
  public void run(){
    while(true){
      System.out.println("试图停止绘画过程");
      paintBoard.stopDraw();
      System.out.println("停止绘画过程完成");
    }
  }
}

问题的发生和解决

刚才的死锁原因是run()函数中有一个无限循环,一个线程进入后会在其中往复操作,这使它永远不会放弃对this的锁定,结果导致其它线程无法获得this的锁定而进入stopDraw函数。
我们把修饰run函数的synchronized取消就能解决问题。 run函数中不会改变任何量,这种函数是不该加上synchronized的。

两个线程争抢资源造成的死锁.

public class Desk{
  private Object fork=new Object();
  private Object knife=new Object();
 
  public void eatForLeft(){
    synchronized(fork){
      System.out.println("左撇子拿起叉");
      sleep(1);
      synchronized(knife){
        System.out.println("左撇子拿起刀");
        System.out.println("左撇子开始吃饭");
      }
    }
  }
 
  public void eatForRight(){
    synchronized(knife){
      System.out.println("右撇子拿起刀");
      sleep(1);
      synchronized(fork){
        System.out.println("右撇子拿起叉");
        System.out.println("右撇子开始吃饭");
      }
    }
  }
 
  private void sleep(int second){
    try{
      Thread.sleep(second*1000);
    }
    catch(InterruptedException ex){
      ex.printStackTrace();
    }
  }
 
  public static void main(String[] args){
    Desk desk=new Desk();
    new LeftHand(desk);
    new RightHand(desk);
  }
}

public class LeftHand implements Runnable{
  private Desk desk;
 
  public LeftHand(Desk desk){
    this.desk=desk;
   
    Thread th=new Thread(this);
    th.start();
  }
 
  public void run(){
    while(true){
      desk.eatForLeft();
    }
  }
}

public class RightHand implements Runnable{
  private Desk desk;
 
  public RightHand(Desk desk){
    this.desk=desk;
   
    Thread th=new Thread(this);
    th.start();
  }
 
  public void run(){
    while(true){
      desk.eatForRight();
    }
  }
}

问题的发生和解决

这部分程序中于两个线程都要获得两个对象的锁定才能执行实质性操作,但运行起来却发现其它线程持有了自己需要的另一个锁定,于是停在Wait Set中等待对方释放这个锁定,结果造成了死锁。
解决这个问题的方法是保证锁对象的持有顺序,如果两个加上了同步的函数都是先刀后叉的形式则不会发生问题。

小结

同步不是改善程序安全性的灵丹妙药,盲目同步也会导致严重的问题-死锁.
某线程持续不退出同步函数会造成死锁.解决方法是去掉或更换不正确的同步。
两个线程都等待对方释放自己需要的资源也会造成死锁.这种情况的解决方法是确保同步锁对象的持有顺序。

posted on 2008-02-22 14:11 和风细雨 阅读(770) 评论(0)  编辑  收藏 所属分类: 线程


只有注册用户登录后才能发表评论。


网站导航: