Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/348.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么添加此防御副本可以避免死锁?_Java_Multithreading_Concurrency - Fatal编程技术网

Java 为什么添加此防御副本可以避免死锁?

Java 为什么添加此防御副本可以避免死锁?,java,multithreading,concurrency,Java,Multithreading,Concurrency,这是原始代码。由于updateProgress方法调用另一个可能获取或不获取另一个锁的方法,因此该程序可能最终死锁。我们在不知道是否按正确顺序操作的情况下获得了这两个锁 import java.io.*; import java.net.URL; import java.util.ArrayList; class Downloader extends Thread { private InputStream in; private OutputStream out; private

这是原始代码。由于
updateProgress
方法调用另一个可能获取或不获取另一个锁的方法,因此该程序可能最终死锁。我们在不知道是否按正确顺序操作的情况下获得了这两个锁

import java.io.*;
import java.net.URL;
import java.util.ArrayList;

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

这样做可以避免调用持有锁的“alien”方法,并减少在
updateProgress
中获取的原始锁被持有的时间。我理解为什么它减少了锁的持有时间,但不理解它如何避免调用持有锁的外来方法。这是我的思路

  • 它创建arraylist
    侦听器的克隆。此克隆是一个单独的对象,它包含原始
    侦听器所具有的确切元素

  • 现在这是线程安全的,因为现在您有一个“本地”副本,至少是该特定线程的本地副本,而另一个线程对其本地副本所做的不会影响您

  • 您可以通过
    onProgress
    方法更新侦听器。但是,此更改仅适用于您的
    侦听器
    副本

  • updateProgress
    返回但“本地”更改如何传播到“原始”侦听器
    ?因为它是一个克隆,所以它们是独立的对象,但是它们如何相互通信更新


  • 这就是我一直坚持的部分。

    一个真正的病理病例应该是:

    • 其中一个侦听器启动一个
      线程
    • 该线程试图调用
      下载程序上的同步方法
    • 调用侦听器的线程(启动该新线程的线程)在启动的线程上调用
      join
    比如:

    class Pathological implements ProgressListener {
      // Initialize in ctor.
      final Downloader downloader;
    
      @Override void onProgress(int n) {
        Thread t = new Thread(() -> downloader.removeListener(Pathological.this));
        t.start();
        t.join();
      }
    }
    
    在这种情况下会出现死锁,因为当第一个线程持有监视器时,启动的线程无法前进


    获取防御副本可以避免这种情况,因为调用
    physical.onProgress
    时,第一个线程没有占用监视器;但我还是更愿意使用一种替代列表实现来处理并发访问,例如,
    CopyOnWriteArrayList

    问题的标题与最后的问题不匹配。这个问题的标题是关于避免僵局,这一点已经得到了解决。他描述了一个场景,死锁是如何发生的,并且你自己在这个问题中给出了一个结论:在持有锁时,你不调用“外来方法”来避免死锁

    因为这似乎已经被理解了,你的问题实际上是一个完全不同的问题。您会问,“如何将“本地”更改传播到“原始”侦听器?”

    答案是,没有这样的局部变化。你名单上的第三个项目是错误的。本地副本不仅是当前线程的本地副本,而且是
    updateProgress
    方法的本地副本,任何其他代码都看不到

    因此,当通过
    addListener
    removeListener
    对侦听器列表进行修改时,无论是哪个线程进行修改,都会直接影响对象的
    listener
    字段引用的列表,该字段仍然与以前相同。但是此更改不会影响本地副本,
    updateProgress
    方法正在迭代。由于
    updateProgress
    只在本地副本上迭代,因此它永远不会有任何需要传播到原始列表的修改

    请注意,在单线程场景中甚至需要此副本。许多
    列表
    实现,尤其是
    ArrayList
    ,不支持在有人对其进行迭代时进行修改,即使修改是由执行迭代的同一线程进行的(除了通过用于迭代的相同
    迭代器
    修改列表,此处不适用)


    替代方案有:在迭代时不需要复制,但在修改列表时需要复制;或者是多播模式,在删除侦听器时可能只需要(部分)复制,但在侦听器数量变大时可能会变得效率低下(但对于小数目的侦听器非常有效)。请参阅此模式的示例。

    旁白:此处的
    CopyOnWriteArrayList
    可能会更好。我认为这一切都是为了减轻一种病态情况,即其中一个侦听器在其
    onProgress
    实现过程中调用
    addListener
    RemovelListener
    。@OliverCharlesworth,但肯定如此这与死锁无关,而是会导致
    ConcurrentModificationException
    @AndyTurner-我认为原始代码会死锁。通过输入
    updateProgress
    ,然后调用
    listener.onProgress
    ,然后调用
    addListener
    ,尝试获取锁。噢,除了
    同步的方法是可重入的之外。所以我不明白这里到底会出现什么问题。甚至比我想象的还要病态;)@OliverCharlesworth我认为唯一合适的回答是“muhahahaha”。或者使用多播模式,这在听众人数较少时尤其有效。
    
    class Pathological implements ProgressListener {
      // Initialize in ctor.
      final Downloader downloader;
    
      @Override void onProgress(int n) {
        Thread t = new Thread(() -> downloader.removeListener(Pathological.this));
        t.start();
        t.join();
      }
    }