Java “为什么?”;header.get()+;footer.get();使用单线程执行器时是否会导致死锁?

Java “为什么?”;header.get()+;footer.get();使用单线程执行器时是否会导致死锁?,java,multithreading,Java,Multithreading,这是中的清单8.1: 公共类线程死锁{ ExecutorService exec=Executors.newSingleThreadExecutor(); 公共类RenderPageTask实现可调用{ 公共字符串调用()引发异常{ 未来页眉、页脚; header=exec.submit(newloadfiletask(“header.html”); footer=exec.submit(newloadfiletask(“footer.html”); String page=renderBody

这是中的清单8.1:

公共类线程死锁{
ExecutorService exec=Executors.newSingleThreadExecutor();
公共类RenderPageTask实现可调用{
公共字符串调用()引发异常{
未来页眉、页脚;
header=exec.submit(newloadfiletask(“header.html”);
footer=exec.submit(newloadfiletask(“footer.html”);
String page=renderBody();
//Will deadlock——等待子任务结果的任务
返回页眉.get()+页面+页脚.get();
}
}
}
它在里面

第8章:线程池>第8.1.1节线程饥饿死锁

标题如下:

“在单线程
执行器中死锁的任务。不要这样做。”

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

相关章节文本:

8.1.1线程饥饿死锁

如果依赖于其他任务的任务在线程池中执行,它们可能会死锁。在单线程执行器中,向同一执行器提交另一个任务并等待其结果的任务将始终处于死锁状态。第二个任务位于工作队列上,直到第一个任务完成,但第一个任务不会完成,因为它正在等待第二个任务的结果。如果所有线程都在执行被阻塞的任务,等待工作队列上的其他任务,那么在较大的线程池中也会发生同样的情况。这被称为线程饥饿死锁,每当池任务启动无限阻塞等待某个资源或条件时,就会发生这种情况,该资源或条件只能通过另一个池任务的操作才能成功,例如等待另一个任务的返回值或副作用,除非您可以保证池足够大

清单8.1中的
ThreadDeadlock
说明了线程饥饿死锁
RenderPageTask
向执行器提交两个附加任务,以获取页眉和页脚,呈现页面正文,等待页眉和页脚任务的结果,然后将页眉、正文和页脚组合到完成的页面中。对于单线程执行器,
ThreadDeadlock
将始终死锁。类似地,如果池不够大,任务之间通过屏障进行协调也可能导致线程饥饿死锁


是的,我打赌
RenderPageTask
提交到与其他任务相同的执行器池,因此,另一个任务在
RenderPageTask
完成之前不会启动-但这永远不会发生-我们有死锁一旦
RenderPageTask
的实例提交到提交其任务的同一执行器实例,实际的死锁就会发生

例如,添加

exec.submit(new RenderPageTask());
你会遇到僵局

当然,这可以被认为是周围代码的问题 (也就是说,您可以简单地定义并记录您的
RenderPageTask
不应提交到此executor实例),但良好的设计将完全避免此类陷阱


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

我没有看到任何死锁。
LoadFileTask
做什么?这本书是免费提供的吗?如果不花大约25美元,我似乎无法阅读你所指的章节。此外,我们还缺少一些代码,比如什么是
LoadFileTask
?这使得分析问题或解释问题死锁的原因变得困难。虽然此代码段不包含任何可执行文件,因此没有可死锁的内容,因为没有可运行的内容。书中是否说RenderPageTask本身已经在同一执行器中运行?这将导致死锁…没有代码显示<代码>加载文件任务
是一项虚构的任务。我将添加本章的文本…在单线程执行器中,向同一执行器提交另一个任务并等待其结果的任务将始终死锁。第二个任务位于工作队列中,直到第一个任务完成,但第一个任务不会完成,因为它正在等待第二个任务的结果。啊,是的,现在我明白了。单线程执行器按照提交的顺序处理所有可调用项,但第一个可调用项无法返回,因为它取决于它提交的第二个和第三个可调用项。在旁注中,这就是为什么我更喜欢使用委托,而不是使用未来的对象和线程阻塞。我看不到
RenderPageTask
提交给执行者的位置。代码中唯一的
submit
s用于页眉和页脚子任务。我知道如果整个“呈现”任务提交给执行者,它将在单个线程池中死锁,因为它正在等待它的子任务…这些子任务需要该线程来工作。我现在明白了<代码>渲染页面任务是可调用的。为了执行,它必须在我们没有展示的其他代码中提交给执行者。刚才标记为重复的问题清楚地说明了这一点。
exec.submit(new RenderPageTask());