为什么" header.get()+ footer.get()"使用单线程执行程序时导致死锁?

时间:2015-03-17 14:33:27

标签: java multithreading

这是Java Concurrency In Practice中的8.1列表:

public class ThreadDeadlock  {
   ExecutorService exec = Executors.newSingleThreadExecutor();

   public class RenderPageTask implements Callable<String>  {
      public String call() throws Exception  {
         Future<String> header, footer;
         header = exec.submit(new LoadFileTask("header.html"));
         footer = exec.submit(new LoadFileTask("footer.html"));
         String page = renderBody();

         //Will deadlock -- task waiting for result of subtask
         return header.get() + page + footer.get();
      }
   }
}

它在

第8章:线程池&gt;第8.1.1节线程饥饿死锁

并有标题:

  

&#34;在单线程Executor中死锁的任务。 不要这样做。&#34;

为什么会导致死锁?调用 think header.get(),然后调用footer.get(),每个结果都附加到字符串中。为什么单个线程执行程序不足以一个接一个地运行这些执行程序?

相关章节文字:

  

8.1.1线程饥饿死锁

     

如果依赖于其他任务的任务在线程池中执行,则它们可能会死锁。在单线程执行程序中,将另一个任务提交给同一个执行程序并等待其结果的任务将始终死锁。第二个任务位于工作队列中,直到第一个任务完成,但第一个任务将无法完成,因为它正在等待第二个任务的重新启动。如果所有线程都在执行被阻塞的任务,等待仍在工作队列中的其他任务,则在较大的线程池中也会发生同样的情况。这称为线程饥饿死锁,并且只要池任务启动无限制阻塞等待某些资源或条件只能通过另一个池任务的操作(例如等待返回值),就会发生这种情况。或另一项任务的副作用,除非你能保证游泳池足够大。

     清单8.1中的

ThreadDeadlock说明了线程饥饿死锁。 RenderPageTaskExecutor提交另外两个任务来获取页眉和页脚,呈现页面正文,等待页眉和页脚任务的结果,然后组合页眉,正文和页脚进入完成的页面。使用单线程执行程序,ThreadDeadlock将始终死锁。同样,如果池不够大,任务与屏障协调的任务也可能导致线程饥饿死锁。

2 个答案:

答案 0 :(得分:5)

只要RenderPageTask的实例被提交到提交其任务的同一个执行器实例,就会发生实际的死锁。

例如,添加

exec.submit(new RenderPageTask());

你将遇到僵局。

当然这可以被认为是周围代码的问题 (也就是说,你可以简单地定义并记录你的RenderPageTask不应该提交给这个执行者实例),但一个好的设计可以完全避免这些陷阱。

可能的解决方案是使用ForkJoinPool,它使用工作窃取来避免这种形式的可能死锁。

答案 1 :(得分:3)

是的,我敢打赌,RenderPageTask被提交到与其他任务相同的执行程序池,因此在RenderPageTask完成之前,其他任务才会开始 - 但这将永远不会发生 - 我们已经死锁