Java 中的死锁(举例说明)
Deadlocking in Java (with an example)
我正在阅读一个关于 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) {
}
}
}
假设您有两个 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
。一旦您不能完全控制获取哪些锁,您就会面临死锁的可能性。
原因是 listener.onProgress()
在您的锁定范围之外。如果它试图获得对已被阻止的对象的锁定,但随后需要对您的对象进行锁定,则可能会发生死锁。
我正在阅读一个关于 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) {
}
}
}
假设您有两个 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
。一旦您不能完全控制获取哪些锁,您就会面临死锁的可能性。
原因是 listener.onProgress()
在您的锁定范围之外。如果它试图获得对已被阻止的对象的锁定,但随后需要对您的对象进行锁定,则可能会发生死锁。