【发布时间】:2018-06-14 17:57:26
【问题描述】:
这是原始代码。这个程序最终可能会死锁,因为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) { }
}
}
教科书的作者建议在迭代之前更改updateProgress 以创建ArrayList<ProgressListener> listeners 的防御性副本。
private void updateProgress(int n) {
ArrayList<ProgressListener> listenersCopy;
synchronized(this) {
listenersCopy = (ArrayList<ProgressListener>)listeners.clone();
}
for (ProgressListener listener: listenersCopy)
listener.onProgress(n);
}
这样做可以避免在持有锁的情况下调用“外星人”方法,并减少在updateProgress 中获取的原始锁被持有的时间。我理解为什么它会减少持有锁的时间,但不理解它如何避免在持有锁的情况下调用外星方法。这是我的思路。
它创建数组列表
listeners的克隆。此克隆是一个单独的对象,其中包含原始listener所具有的确切元素。-
现在这是线程安全的,因为现在您有一个“本地”副本,至少对于该特定线程来说是本地的,并且另一个线程对其本地副本所做的事情不会影响您。
李> 您通过
onProgress方法更新侦听器。但是,此更改仅适用于您的listeners副本。updateProgress返回但“本地”更改如何传播到“原始”listeners?由于它是一个克隆,它们是独立的对象,但它们如何将它们的更新相互通信?
这就是我坚持的部分。
【问题讨论】:
-
旁白:
CopyOnWriteArrayList在这里可能会更好。 -
我认为这一切都是为了减轻其中一位听众在其
onProgress实施期间调用addListener或removeListener的病态情况。 -
@OliverCharlesworth 但肯定这与死锁没有任何关系,而是会导致
ConcurrentModificationException。 -
@AndyTurner - 我认为原始代码会死锁。通过输入
updateProgress获取锁,然后调用listener.onProgress,然后(病态地)调用addListener,尝试获取锁。 -
哦,除了
synchronized方法是可重入的。所以我不明白这里到底会出现什么问题。
标签: java multithreading concurrency