为什么" header.get()+ footer.get()"使用单线程执行程序时导致死锁? [英] Why does "header.get() + footer.get()" result in deadlock, when using a single threaded Executor?

查看:106
本文介绍了为什么" header.get()+ footer.get()"使用单线程执行程序时导致死锁?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是 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章:线程池>第8.1.1节线程饥饿死锁

Chapter 8: Thread Pools > Section 8.1.1 Thread starvation deadlock

并有标题:


单线程执行程序不要这样做。

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

Why does this result in a deadlock? I thought header.get() is called, and then footer.get() is called, which each result appended to the string. Why wouldn't a single threaded Executor be enough to run these one after the other?

相关章节文本:

8.1.1线程饥饿死锁

8.1.1 Thread starvation deadlock

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

If tasks that depend on other tasks execute in a thread pool, they can deadlock. In a single-threaded executor, a task that submits another task to the same executor and waits for its result will always deadlock. The second task sits on the work queue until the first task completes, but the first will not complete because it is waiting for the resut of the second task. The same thing can happen in larger thread pools if all threads are executing tasks that are blocked waiting for other tasks still on the work queue. This is called thread starvation deadlock, and can occur whenever a pool task initiates an unbounded blocking wait for some resource or condition that can succeed only through the action of another pool task, such as waiting for the return value or side effect of another task, unless you can guarantee that the pool is large enough.

清单8.1中的ThreadDeadlock 说明线程饥饿死亡。 RenderPageTask Executor提交另外两个任务 o获取页眉和页脚,呈现页面正文,等待页眉和页脚任务的结果,然后将页眉,正文和页脚组合到完成的页面中。使用单线程执行程序时, ThreadDeadlock 将始终死锁。类似地,如果池不够大,任务之间与屏障协调的任务也可能导致线程饥饿死锁。

ThreadDeadlock in Listing 8.1 illustrates thread starvation deadldock. RenderPageTask submits two additional tasks to the Executor o fetch the page header and footer, renders the page body, waits for the results of the header and footer tasks, and then combines the header, body, and footer into the finished page. With a single-threaded executor, ThreadDeadlock will always deadlock. Similarly, tasks coordinating amongst themselves with a barrier could also cause thread starvation deadlock if the pool is not big enough.


推荐答案

只要将 RenderPageTask 的实例提交给提交其任务的同一个执行程序实例,就会发生实际的死锁。

The actual deadlock will occur will occur as soon as an instance of RenderPageTask is submitted to the very same executor instance where it submits its task.

例如,添加

exec.submit(new RenderPageTask());

您将遇到死锁。

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

Of course this can be considered a problem of the surrounding code (i.e., you could simply define and document that your RenderPageTask should not be submitted to this executor instance), but a good design would avoid such pitfalls entirely.

一个可能的解决办法就是使用 ForkJoinPool ,它使用工作窃取来避免这种形式的可能死锁。

A possible solution for this would be to use ForkJoinPool, which uses work stealing to avoid this form of possible deadlocks.

这篇关于为什么&quot; header.get()+ footer.get()&quot;使用单线程执行程序时导致死锁?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆