문제

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]를 호출하기 때문에 위의 코드가 교착 상태에 빠질 가능성이 있다고 말합니다.올바른 순서 없이 두 개의 잠금을 획득하므로 교착 상태가 발생할 수 있습니다.

위 시나리오에서 교착 상태가 어떻게 발생하는지 설명할 수 있는 사람이 있습니까?

미리 감사드립니다.

도움이 되었습니까?

해결책

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. 의 잠금을 해제합니다.
  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);
    // ...
  }
}

교착 상태를 피하는 한 가지 방법은 수행된 작업을 연기하는 것입니다. 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