2015-09-04 45 views
3

我读的Java并发的话题,这里就是作者写道:在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) { 
     } 
    } 

} 

回答

3

比方说,你有Downloader两个实例,叫他们dlAdlB。你也有一个监听器,它只是来自那些下载者删除自身,以及最初连接到两个下载:

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执行代码。当你没有完全控制获得的锁时,你会打开自己的死锁的可能性。

+0

谢谢。 “监听器调用”动作发生在单独的线程中(即线程3,线程4与线程1,2并行),还是发生在监听器的onProgress()方法中? – CoolOne

+0

好吧,两者。它发生在侦听器的'onProgress()'方法中,但是请记住有_two_侦听器,每个侦听器都在独立线程上调用onProgress。在thread1上调用dlA的监听器(通过在thread1上调用的dlA.updateProgress中的listener.onProgress(n)调用),并且在thread2上调用dlB的监听器。但是,你甚至可以用_one_ listener来完成这个工作,它将自己从dlA和dlB中移除。 – yshavit

+0

我稍微更新了答案,以显示只有一个听众会发生什么情况。 – yshavit

0

原因是listener.onProgress()超出了您的锁定范围。如果它试图获得对已经被阻塞的东西的锁定,但是某些东西需要锁定对象,则会发生死锁。