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) { }
}
}
教科书的作者建议在迭代遍历 ArrayList<ProgressListener> listeners
之前,将 updateProgress
更改为创建 防御性拷贝。
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
可能会更好。 - Andy TurneronProgress
实现期间调用addListener
或removeListener
的病态情况。 - Oliver Charlesworthsynchronized
方法是可重入的。所以我不明白这里预期出现了什么问题。 - Oliver Charlesworthnew ArrayList<>(listeners)
这一事实让我对该书的权威性和专业水平产生了质疑。 - VGR