这里的代码是否发生了线程饥饿死锁?

4
//code taken from java concurrency in practice

  package net.jcip.examples;

import java.util.concurrent.*;


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

    public class LoadFileTask implements Callable<String> {
        private final String fileName;

        public LoadFileTask(String fileName) {
            this.fileName = fileName;
        }

        public String call() throws Exception {
            // Here's where we would actually read the file
            return "";
        }
    }

    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();
        }


    }
}

这段代码来自《Java并发编程实战》,根据作者的说法,“ThreadStarvationDeadlock”会在这里发生。请帮助我找出在哪里以及如何发生“ThreadStarvationDeadlock”。谢谢。


你需要提供更多的代码,因为我们假设有一些缺失的部分,而我们可能会错误地假设它们。 - Audrius Meškauskas
3个回答

13

在以下代码行发生了死锁和饥饿:

return header.get() + page + footer.get();

怎么做?
如果我们给程序添加一些额外的代码,这种情况就会发生。可能是以下代码:

    public void startThreadDeadlock() throws Exception
    {
        Future <String> wholePage = exec.submit(new RenderPageTask());
        System.out.println("Content of whole page is " + wholePage.get());
    }
    public static void main(String[] st)throws Exception
    {
        ThreadDeadLock tdl = new ThreadDeadLock();
        tdl.startThreadDeadLock();
    }

导致死锁的步骤:

  1. 任务通过实现类 RenderPageTaskCallable 提交给 exec 以渲染页面。
  2. exec 在单独的线程中启动了 RenderPageTask,该线程是唯一一个按顺序执行提交给 exec 的其他任务的线程。
  3. RenderPageTaskcall() 方法内,又提交了两个任务给 exec。第一个是 LoadFileTask("header.html"),第二个是 LoadFileTask("footer.html")。但由于使用代码 Executors.newSingleThreadExecutor(); 获取的 ExecutorService,如此处所述,使用单个工作线程操作无界队列线程池 并且线程已经分配给 RenderPageTask,因此 LoadFileTask("header.html")LoadFileTask("footer.html") 将被放入无限队列等待它们轮流被该 Thread 执行。
  4. RenderPageTask 返回一个包含 LoadFileTask("header.html") 输出的串联字符串、页面主体和 LoadFileTask("footer.html") 输出的字符串。在这三个部分中,pageRenderPageTask 成功获取。但是只有在单个线程被 ExecutorService 分配后,才能获得其他两个部分。并且线程仅在 RenderPageTaskcall() 方法返回后空闲。但是 RenderPageTaskcall 方法只有在 LoadFileTask("header.html")LoadFileTask("footer.html") 返回后才会返回。因此不允许 LoadFileTask 执行导致了饥饿。每个任务等待其他任务完成导致死锁


  5. 我希望这样可以清楚地解释为什么上述代码出现了线程饥饿死锁问题。

谢谢您的回复。我曾经认为,当任务LoadFileTask("header.html")被提交给执行器时,它将切换到该任务(即LoadFileTask(header.html))并完成,然后当执行器提交任务LoadFileTask(footer.html)时,它也会切换到该任务,在完成后,它将返回父任务RenderPageTask并完成。因此不会出现死锁。但是我忘记了所有任务都将被提交到队列中,而队列具有FIFO结构。因此,由于执行器只有一个线程,这两个LoadFileTask任务永远不会被执行。我的理解正确吗? - user2030415
是的,正确的。为了更好地理解,您应该查看Executors文档的这一部分:http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor()。 - Vishal K

2
我看到的执行器是单线程执行器,它有两个任务要执行。然而这两个任务不依赖于彼此,执行顺序似乎并不重要。因此,在 Future.get 调用中,返回语句只会暂停所需时间来完成一个任务,然后再执行另一个任务。
在你展示的代码中不会出现死锁。
然而,我在代码中看到了另一个任务(RenderPageTask),不清楚哪个执行器实际上正在运行它的代码。如果它是同一个单线程执行器,那么死锁是可能的,因为在主任务返回之前无法处理提交的两个任务(而这个任务只能在两个任务被处理后返回)。

0

从代码本身来看,原因并不是很明显,但是从代码复制的原始书籍中可以看出:RenderPageTask提交了两个额外的任务给执行器以获取页面头部和页脚...

如果RenderPageTask是一个独立于newSingleThreadExecutor的任务,那么就不会有死锁问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接