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调用可能获得另一个锁的外来方法[onProgress]。 由于我们获得了两个没有正确顺序的锁,因此可能发生死锁。
任何人都可以解释在上述场景中是如何发生死锁的吗?
提前致谢。
答案 0 :(得分:3)
最好将synchronized
使用的对象设为私有。
由于您在Downloader
上进行同步,因此您不知道其他线程是否也会在Downloader
上同步。
以下侦听器导致死锁:
MyProgressListener extends ProgressListener {
public Downloader downloader;
public void onProgress(int n) {
Thread t = new Thread() {
@Override
public void run() {
synchronized(downloader) {
// do something ...
}
}
};
t.start();
t.join();
}
}
Downloader d = new Downloader(...);
MyProgressListener l = new MyProgressListener();
l.downloader = d;
d.addListener(l);
d.run();
如果您运行该代码,将会发生以下情况:
updateProgress
并获取对Downloader
MyProgressListener
的{{1}}方法并启动新线程onProgress
t
在这种情况下,主线程在t.join();
完成之前无法进行,但是对于t
完成,主线程必须释放它对t
的锁定,但是赢了因为主线程无法进行而发生 - &gt;的死锁强>
答案 1 :(得分:2)
首先,请回想一下,关键字synchronized
在应用于类时,意味着锁定此方法所属的整个对象。现在,让我们勾勒出另外几个触发死锁的对象:
class DLlistener implements ProgressListener {
private Downloader d;
public DLlistener(Downloader d){
this.d = d;
// here we innocently register ourself to the downloader: this method is synchronized
d.addListener(this);
}
public void onProgress(int n){
// this method is invoked from a synchronized call in Downloader
// all we have to do to create a dead lock is to call another synchronized method of that same object from a different thread *while holding the lock*
DLthread thread = new DLThread(d);
thread.start();
thread.join();
}
}
// this is the other thread which will produce the deadlock
class DLThread extends Thread {
Downloader locked;
DLThread(Downloader d){
locked = d;
}
public void run(){
// here we create a new listener, which will register itself and generate the dead lock
DLlistener listener(locked);
// ...
}
}
避免死锁的一种方法是通过让侦听器的内部队列等待添加/删除来推迟addListener
中完成的工作,并让Downloader
定期自行处理这些工作。这最终取决于Downloader.run
内部工作。
答案 2 :(得分:1)
可能是此代码中的问题:
for (ProgressListener listener: listeners)
listener.onProgress(n);
当一个持有锁的线程调用外部方法时 像这一个(onProgress)然后你不能保证 执行此方法不会尝试获取其他锁, 这可以由不同的线程持有。这可能会导致死锁。
答案 3 :(得分:0)
这是一个典型的例子,展示了作者试图避免的难以调试的问题。
创建课程UseDownloader
并调用downloadSomething
。
随着下载的进行,调用onProgress
方法。由于这是从同步块中调用的,因此Downloader
motinor被锁定。在我们的onProgress
方法中,我们需要锁定自己的资源,在本例中为lock
。因此,当我们尝试在lock
上进行同步时,我们持有Downloader
监视器。
如果另一个主题决定取消下载,则会调用setCanceled
。首先测试done
,然后在lock
监视器上进行同步,然后调用removeListener
。但removeListener
需要Downloader
锁定。
这种僵局很难找到,因为它并不经常发生。
public static final int END_DOWNLOAD = 100;
class UseDownloader implements ProgressListener {
Downloader d;
Object lock = new Object();
boolean done = false;
public UseDownloader(Downloader d) {
this.d = d;
}
public void onProgress(int n) {
synchronized(lock) {
if (!done) {
// show some progress
}
}
}
public void downloadSomething() {
d.addListener(this);
d.start();
}
public boolean setCanceled() {
synchronized(lock) {
if (!done) {
done = true;
d.removeListener(this);
}
}
}
}
答案 4 :(得分:-1)
以下示例导致死锁,因为MyProgressListener尝试获取已经获取的Downloader锁。
class MyProgressListener extends ProgressListener {
private Downloader myDownloader;
public MyProgressListener(Downloader downloader) {
myDownloader = downloader;
}
public void onProgress(int n) {
// starts and waits for a thread that accesses myDownloader
}
}
Downloader downloader = new Downloader(...);
downloader.addListener(new MyListener(downloader));
downloader.run();