1
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]。由于我们在没有正确顺序的情况下获得了两个锁,因此可能会发生死锁。

谁能解释在上述情况下死锁是如何发生的?

提前致谢。

4

5 回答 5

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. 的方法MyProgressListener被调用并启动onProgress新线程t
  3. 主线程到达t.join();

在这种情况下,主线程在完成之前无法继续t,但t要完成,主线程必须释放它对 的锁定Downloader,但这不会发生,因为主线程无法继续 ->死锁

于 2014-07-31T04:46:05.713 回答
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内部工作。

于 2014-07-31T04:44:55.317 回答
1

可能是这段代码的问题:

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

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

于 2014-07-31T04:24:50.623 回答
0

这是一个经典示例,它显示了作者试图避免的那种难以调试的问题。

UseDownloader被创建并被downloadSomething调用。

随着下载的进行,该onProgress方法被调用。由于这是从同步块中调用的,因此Downloader电机被锁定。在我们的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);
        }
      }
    }
  }
于 2014-07-31T21:17:40.377 回答
-1

以下示例会导致死锁,因为 MyProgressListener 尝试在已获取下载器锁时获取它。

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();
于 2014-07-31T04:12:36.047 回答