为什么" header.get()+ footer.get()"使用单线程执行程序时导致死锁? [英] Why does "header.get() + footer.get()" result in deadlock, when using a single threaded Executor?
问题描述
这是 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
并有标题:
单线程
执行程序$ c中的死锁任务$ c>。不要这样做。
为什么这会导致死锁?我想 调用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.
这篇关于为什么" header.get()+ footer.get()"使用单线程执行程序时导致死锁?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!