我正在阅读关于Java并发的主题,这是作者写的内容:
因为addListener(),removeListener()和updateProgress()全部同步, 多个线程可以调用它们而不会踩到彼此的脚趾。但 陷阱隐藏在这段代码中,即使只有陷阱,也可能导致死锁 一把锁正在使用中。 问题是updateProgress()调用外来方法 - 它知道的方法 没什么。该方法可以做任何事情,包括获得另一个 锁。如果确实如此,那么我们已经获得了两把锁而不知道我们是否已经 以正确的顺序完成。正如我们刚才所见,这可能导致僵局。
请你解释一下,如果我们总是首先获得第一个锁(因为updateProgress方法是同步的!!),我们怎么能以错误的顺序获得两个锁,这意味着第二个来自两个锁作者所说的将永远是第二位的?
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) {
}
}
}
答案 0 :(得分:3)
假设您有两个Downloader
个实例,请将其称为dlA
和dlB
。你也有一个监听器,它只是从这两个下载器中删除自己,并且最初连接到两个下载器:
Downloader dlA = new Downloader(...);
Downloader dlB = new Downloader(...);
ProgressListener listener = new ProgressListener() {
@Override
public void onProgress(int n) {
dlA.removeListener(this);
dlB.removeListener(this);
}
}
dlA.addListener(listener);
dlB.addListener(listener);
好的,现在当两个线程同时调用updateProgress
时会发生什么,一个在dlA
上,另一个在dlB
上?
thread1: dlA.updateProgress(1) thread2: dlB.updateProgress(1)
gets lock on dlA gets lock on dlB
calls listener.onProgress(1) calls listener.onProgress(1)
calls dlA.removeListener(this) calls dlA.removeListener(this)
succeeds tries to get lock on dlA
calls dlB.removeListener (stuck until thread1 finishes
tries to get lock on dlB dlA.updateProgress)
(stuck until thread2 finishes
dlB.updateProgress)
你去,死锁!
问题在于侦听器代码可以执行任何想要的操作,包括获取锁(直接或间接),并且在您的线程保持this
时执行该代码。当你无法完全控制所获得的锁定时,你就会陷入僵局的可能性。
答案 1 :(得分:0)
原因是listener.onProgress()
超出了您的锁定范围。如果它试图获取已经被阻止的东西的锁定,但是那些东西需要锁定你的对象,就会发生死锁。