【发布时间】:2019-06-10 15:31:32
【问题描述】:
我正在看《七周的七个并发模型》一书,第2章有关于异形方法的描述。
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)
{
}
}
}
因为 addListener()、removeListener() 和 updateProgress() 都是 同步,多个线程可以调用它们而不需要踩一个 另一个脚趾。但是这段代码中潜伏着一个陷阱,可能导致 即使只有一个锁在使用,也会出现死锁。问题是 updateProgress() 调用了一个外星方法——一个它什么都不知道的方法 关于。这种方法可以做任何事情,包括获得另一个 锁。如果是这样,那么我们在不知道的情况下获得了两个锁 我们是否按照正确的顺序这样做了。正如我们刚刚看到的,这可以 导致僵局。唯一的解决方案是避免调用外来方法 同时拿着一把锁。实现这一目标的一种方法是进行防御 迭代之前的监听器副本:
private void updateProgress(int n) {
ArrayList<ProgressListener> listenersCopy;
synchronized(this) {
listenersCopy = (ArrayList<ProgressListener>)listeners.clone();
}
for (ProgressListener listener: listenersCopy)
listener.onProgress(n);
}
看了这个解释,还是不明白onProgress方法怎么会导致死锁,为什么克隆监听列表可以避免这个问题。
【问题讨论】:
-
附带说明,您最好使用
listenersCopy = new ArrayList<>(listeners);。 -
@Holger 使用 ArrayList 构造函数有什么好处?我检查了源代码,似乎它们都在后台使用 Arrays.copyOf 方法。
-
您不需要执行未经检查的类型转换。而
clone()通常是要避免的。
标签: java concurrency jvm