Frage

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

Der obige Code stammt aus dem Buch "Sieben Parallelitätsmodelle in sieben Wochen".Das Buch besagt, dass der obige Code Potenzial für den Deadlock hat, da die synchronisierte Methode UpdateProgress eine fremde Methode [OnProgress] aufruft, die möglicherweise eine andere Sperre erhält.Da wir zwei Schlösser ohne richtige Reihenfolge erwerben, kann der Deadlock auftreten.

Kann jemand erklären, wie der Deadlock im obigen Szenario passiert?

Danke im Voraus.

War es hilfreich?

Lösung

Es ist am besten, die Objekte zu erstellen, mit denen Sie verwenden synchronized privat.

Da Sie auf dem synchronisieren Downloader, Sie wissen nicht, ob andere Threads auf dem synchronisieren Downloader zu.

Der folgende Listener verursacht einen Deadlock:

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

Code, der blockiert:

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

Folgendes passiert, wenn Sie diesen Code ausführen:

  1. der Hauptfaden erreicht die updateProgress und erwirbt eine Sperre für die Downloader
  2. der MyProgressListenervon onProgress methode wird aufgerufen und der neue Thread t wird gestartet
  3. der Hauptfaden erreicht t.join();

In dieser Situation kann der Hauptthread erst fortfahren, wenn t ist fertig, aber für t zum Abschluss müsste der Hauptthread seine Sperre für das freigeben Downloader, aber das wird nicht passieren, da der Hauptthread nicht fortfahren kann -> Deadlock

Andere Tipps

Erinnern Sie sich zunächst daran, dass das Schlüsselwort synchronized, wenn es auf eine Klasse angewendet wird, impliziert das Sperren des gesamten Objekts, zu dem diese Methode gehört.Lassen Sie uns nun noch ein paar Objekte skizzieren, die den Deadlock auslösen:

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

Eine Möglichkeit, die Sackgasse zu vermeiden, besteht darin, die geleistete Arbeit zu verschieben addListener indem interne Warteschlangen von Zuhörern darauf warten, hinzugefügt / entfernt zu werden, und haben Downloader sich regelmäßig selbst um diese kümmern.Dies hängt letztendlich davon ab Downloader.run inneres Arbeiten natürlich.

Wahrscheinlich das Problem in diesem Code:

generasacodicetagpre.

Wenn ein Thread, der ein Sperre enthält, ein externes Verfahren anruft Wie dieses (aufprogress) kannst du das nicht garantieren Die Implementierung dieser Methode wird nicht versuchen, andere Sperre zu erhalten, das könnte von einem anderen Thread gehalten werden.Dies kann zu einem Deadlock führen.

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();
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top