سؤال

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

الكود أعلاه مأخوذ من كتاب "سبعة نماذج متزامنة في سبعة أسابيع".يقول الكتاب أن الكود أعلاه ينطوي على احتمال حدوث طريق مسدود حيث يستدعي تحديث الطريقة المتزامنة طريقة غريبة [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, ، عند تطبيقه على فئة ما، يعني تأمين الكائن بالكامل الذي تنتمي إليه هذه الطريقة.الآن، دعونا نرسم اثنين آخرين من الكائنات التي تؤدي إلى حالة الجمود:

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 العمل الداخلي بالطبع.

ربما المشكلة في هذا الرمز:

giveacodicetagpre.

عندما يستدعي مؤشر ترابط واحد، الذي يحمل قفل، طريقة خارجية مثل هذا واحد (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