由需要线程的方法引起的死锁

时间:2016-11-29 15:59:49

标签: java multithreading downloading

我目前无法找到正确的方法。

我有一个固定线程池为64的ExecutorService。我正在请求下载一种Book(一次一个)。要下载我需要的书:下载书籍信息,下载页面信息,然后下载书的一部分。当我要求下载一本书时,我得到了每一页信息,并以同样的方法下载了本书的那些小部分。问题是下载书籍的那些小部分也是异步完成的(需要另一个线程),但当时所有64个线程都被页面下载线程占用。我想出了添加另一个ExecutorService或将线程池提升到更大的数字,如256.但这感觉不太对劲。我还有其他选择吗?

问题的步骤和位置摘要:

  1. 下载图书信息
  2. 下载页面:

    • 页面信息
    • 页面的部分内容 - 死锁 - 超出线程。

      @Override
      public Book getBook(int bookId) {
          Book book = books.get(bookId);
          if (book == null) {
              HttpURLConnection conn = factory.getBook(bookId);
              String s = read(conn);
              book = interpret.readBook(s);
      
              books.put(book.getId(), book);
          }
      
          return book;
      }
      
      @Override
      public Page getPage(int bookId, int pageNum) {
          String s = read(factory.getPage(bookId, pageNum));
          List<Integer> eIds = interpret.readExercises(s);
          List<Exercise> exercises = new ArrayList<>(eIds.size());
          CountDownLatch latch = new CountDownLatch(eIds.size());
      
          System.out.println("D: Requesting to dl page " + bookId + '>' + pageNum);
          for (int eId : eIds) {
              System.out.println("eId" + eId);
              service.submit(() -> {
                  try {
                      // The code here does not execute to the lack of free threads
                      System.out.println("D: Requesting to dl exer " + eId);
                      String sE = read(factory.getExercise(bookId, eId));
                      Exercise exercise = interpret.readExercise(sE);
                      exercises.add(exercise);
                      latch.countDown();
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              });
          }
      
          try {
              latch.await();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      
          return new Page(pageNum, exercises);
      }
      
      @Override
      public WholeBook getWholeBook(int bookId) {
          Book book = getBook(bookId);
          List<Page> pages = new ArrayList<>(book.getPages().size());
          CountDownLatch latch = new CountDownLatch(book.getPages().size());
          System.out.println("D: Requesting to dl book " + bookId);
          for (int pageNum : book.getPages()) {
              service.submit(() -> {
                  try {
                      Page page = getPage(bookId, pageNum);
                      System.out.println("Got page: " + page);
                      pages.add(page);
                      latch.countDown();
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              });
          }
      
          try {
              System.out.println("Waiting for book " + bookId);
              latch.await();
          } catch (InterruptedException e) {
              e.printStackTrace();
              return null; // Better to return null rather than corrupted data
          }
      
          return new WholeBook(book, pages);
      }
      
  3. 输出的结尾是: D: Requesting to dl page 10753>67 eId235082 eId235092 之后它停止(技术上运行但没有做任何事情)

    当我中断线程(使用调试器)时,堆栈跟踪点指向#getPage,并且更精确地指向latch.await()

2 个答案:

答案 0 :(得分:2)

由于您正在执行两种不同类型的任务,而第二项是第一项任务的子任务,因此您最终会让执行程序充满第一项任务,这些任务自完成以来无法完成子任务无法执行。虽然这不是死锁的典型例子,但我认为它符合条件。

我处理这个问题的方法是删除getPage()中执行程序的使用。如果由于某种原因(虽然我没有看到任何正当理由)您希望/需要使用多个线程保持getPage(),您必须提供单独的Executor才能使用,所以子任务总是有机会完成。

答案 1 :(得分:0)

从技术上讲,这不是你报告的死锁。你的线程用完了。

看起来你的线程正在进行大量的I / O工作(这很好),但如果你没有关闭这些连接,那么任务可能没有完成,并且ThreadPool无法重新分配线程其他任务。

更新:我知道,你有相互依赖的线程。总的来说这是个坏主意。您可能想要做的是创建一个处理管道。做一个部分,将结果放入队列中。有另一个执行器服务来读取队列以完成请求。