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]。由于我们获取两个锁的顺序不正确,因此可能会发生死锁。

谁能解释一下上述场景中的死锁是如何发生的?

提前致谢。

有帮助吗?

解决方案

最好让你使用的物体 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. MyProgressListeneronProgress 方法被调用并且新线程 t 已开始
  3. 主线程到达 t.join();

在这种情况下,主线程无法继续执行,直到 t 已经完成了,但是对于 t 要完成,主线程必须释放它对 Downloader, ,但这不会发生,因为主线程无法继续 -> 僵局

其他提示

首先,记住关键字 synchronized, 应用于 a 类时,意味着锁定该方法所属的整个对象。现在,让我们画出另外几个触发死锁的对象:

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 当然是内部工作。

可能是此代码中的问题:

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

当一个锁定锁定时,调用外部方法 就像这个(onprogress)那么你就不能保证 实现此方法不会尝试获取其他锁, 这可以由不同的线程持有。这可能会导致僵局。

Here's a classic example that shows the kind of hard-to-debug problems the author is trying to avoid.

The class UseDownloader is created and downloadSomething is called.

As the download progresses, the onProgress method is called. Since this is called from within the synchronized block, the Downloader motinor is locked. Inside our onProgress method, we need to lock our own resource, in this case lock. So when we are trying to synchronize on lock we are holding the Downloader monitor.

If another thread has decided that the download should be canceled, it will call setCanceled. This first tests done so it synchronized on the lock monitor and then calls removeListener. But removeListener requires the Downloader lock.

This kind of deadlock can be hard to find because it doesn't happen very often.

  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);
        }
      }
    }
  }

The following example leads to a deadlock because the MyProgressListener tries to acquire the Downloader lock while it's already acquired.

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();
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top