调用两个同步方法时出现死锁

时间:2014-07-31 03:51:53

标签: java multithreading concurrency java.util.concurrent dining-philosopher

class Downloader extends Thread {
    private InputStream in;
    private OutputStream out;
    private ArrayList<ProgressListener> listeners;
    public Downloader(URL url, String outputFilename) throws IOException {
        in = url.openConnection().getInputStream();
        out = new FileOutputStream(outputFilename);
        listeners = new ArrayList<ProgressListener>();
    }
    public synchronized void addListener(ProgressListener listener) {
        listeners.add(listener);
    }
    public synchronized void removeListener(ProgressListener listener) {
        listeners.remove(listener);
    }

    private synchronized void updateProgress(int n) {
        for (ProgressListener listener: listeners)
            listener.onProgress(n);
    }
    public void run() {
        int n = 0, total = 0;
        byte[] buffer = new byte[1024];
        try {
            while((n = in.read(buffer)) != -1) {
                out.write(buffer, 0, n);
                total += n;
                updateProgress(total);
            }
            out.flush();
        } catch (IOException e) { }
    }
}

上面的代码来自“七周七个并发模型”一书。该书说上面的代码有可能导致死锁,因为同步方法updateProgress调用可能获得另一个锁的外来方法[onProgress]。 由于我们获得了两个没有正确顺序的锁,因此可能发生死锁。

任何人都可以解释在上述场景中是如何发生死锁的吗?

提前致谢。

5 个答案:

答案 0 :(得分:3)

最好将synchronized使用的对象设为私有。

由于您在Downloader上进行同步,因此您不知道其他线程是否也会在Downloader上同步。

以下侦听器导致死锁:

MyProgressListener extends ProgressListener {

     public Downloader downloader;
     public void onProgress(int n) {
         Thread t = new Thread() {
             @Override
             public void run() {
                 synchronized(downloader) {
                     // do something ...
                 }
             }
         };
         t.start();
         t.join();
     }
}

死锁的代码:

Downloader d = new Downloader(...);
MyProgressListener l = new MyProgressListener();
l.downloader = d;
d.addListener(l);
d.run();

如果您运行该代码,将会发生以下情况:

  1. 主线程到达updateProgress并获取对Downloader
  2. 的锁定
  3. 调用MyProgressListener的{​​{1}}方法并启动新线程onProgress
  4. 主线程到达t
  5. 在这种情况下,主线程在t.join();完成之前无法进行,但是对于t完成,主线程必须释放它对t的锁定,但是赢了因为主线程无法进行而发生 - &gt;的死锁

答案 1 :(得分:2)

首先,请回想一下,关键字synchronized在应用于类时,意味着锁定此方法所属的整个对象。现在,让我们勾勒出另外几个触发死锁的对象:

class DLlistener implements ProgressListener {

  private Downloader d;

  public DLlistener(Downloader d){
      this.d = d;
      // here we innocently register ourself to the downloader: this method is synchronized
      d.addListener(this);
  }

  public void onProgress(int n){
    // this method is invoked from a synchronized call in Downloader
    // all we have to do to create a dead lock is to call another synchronized method of that same object from a different thread *while holding the lock*
    DLthread thread = new DLThread(d);
    thread.start();
    thread.join();
  }
}

// this is the other thread which will produce the deadlock
class DLThread extends Thread {
   Downloader locked;
  DLThread(Downloader d){
    locked = d;
  }
  public void run(){
    // here we create a new listener, which will register itself and generate the dead lock
    DLlistener listener(locked);
    // ...
  }
}

避免死锁的一种方法是通过让侦听器的内部队列等待添加/删除来推迟addListener中完成的工作,并让Downloader定期自行处理这些工作。这最终取决于Downloader.run内部工作。

答案 2 :(得分:1)

可能是此代码中的问题:

for (ProgressListener listener: listeners)
            listener.onProgress(n);

当一个持有锁的线程调用外部方法时 像这一个(onProgress)然后你不能保证 执行此方法不会尝试获取其他锁, 这可以由不同的线程持有。这可能会导致死锁。

答案 3 :(得分:0)

这是一个典型的例子,展示了作者试图避免的难以调试的问题。

创建课程UseDownloader并调用downloadSomething

随着下载的进行,调用onProgress方法。由于这是从同步块中调用的,因此Downloader motinor被锁定。在我们的onProgress方法中,我们需要锁定自己的资源,在本例中为lock。因此,当我们尝试在lock上进行同步时,我们持有Downloader监视器。

如果另一个主题决定取消下载,则会调用setCanceled。首先测试done,然后在lock监视器上进行同步,然后调用removeListener。但removeListener需要Downloader锁定。

这种僵局很难找到,因为它并不经常发生。

  public static final int END_DOWNLOAD = 100;

  class UseDownloader implements ProgressListener {
    Downloader d;
    Object lock = new Object();
    boolean done = false;

    public UseDownloader(Downloader d) {
      this.d = d;
    }
    public void onProgress(int n) {
      synchronized(lock) {
        if (!done) {
          // show some progress
        }
      }
    }

    public void downloadSomething() {
      d.addListener(this);
      d.start();
    }

    public boolean setCanceled() {
      synchronized(lock) {
        if (!done) {
          done = true;
          d.removeListener(this);
        }
      }
    }
  }

答案 4 :(得分:-1)

以下示例导致死锁,因为MyProgressListener尝试获取已经获取的Downloader锁。

class MyProgressListener extends ProgressListener {
    private Downloader myDownloader;

    public MyProgressListener(Downloader downloader) {
        myDownloader = downloader;
    }

    public void onProgress(int n) {
        // starts and waits for a thread that accesses myDownloader
    }
}

Downloader downloader = new Downloader(...);
downloader.addListener(new MyListener(downloader));
downloader.run();