質問

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

上記のコードは、書籍「7 週間の同時実行モデル 7 つ」からのものです。この本には、同期メソッド updateProgress が別のロックを取得する可能性のあるエイリアン メソッド[onProgress] を呼び出すため、上記のコードにはデッドロックが発生する可能性があると記載されています。正しい順序でロックを 2 つ取得しないため、デッドロックが発生する可能性があります。

上記のシナリオでデッドロックがどのように発生するかを説明できる人はいますか?

前もって感謝します。

役に立ちましたか?

解決

synchronized Privateで使用するオブジェクトを作るのが最善です。

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. のロックをAQUIREに到達します。
  3. MyProgressListeneronProgressメソッドが呼び出され、新しいスレッドtが開始されました
  4. メインスレッドがt.join();
  5. に達する

    この状況では、tが終了するまで、メインスレッドは手順を処理できませんが、tが終了するためには、メインスレッドがDownloaderのロックを解除する必要がありますが、メインスレッドは手順がないために発生しません - > デッドロック

他のヒント

まず、キーワードを思い出してください。 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);
    // ...
  }
}

デッドロックを回避する 1 つの方法は、実行中の作業を延期することです。 addListener 追加/削除を待機するリスナーの内部キューを用意することで、 Downloader 定期的に自分でそれらの世話をします。これは最終的には Downloader.run もちろん内部作業。

おそらくこのコードの問題:

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

ロックを保持するスレッドが1つのスレッドの場合は、外部メソッドを呼び出します。 このようなもの(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