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

Java 由需要线程的方法引起的死锁,java,multithreading,download,Java,Multithreading,Download,我现在很难想出正确的方法来做这件事 我有一个固定线程池为64的ExecutorService。我请求下载一种书(一次一本)。要下载一本书,我需要:下载书的信息,下载页面信息,然后下载书的一部分。当我要求下载一本书时,我会得到每一页的信息,并且用同样的方法下载书中的那些小部分。问题是下载书籍的这些小部分也是异步完成的(需要另一个线程),但当时所有64个线程都被页面下载线程占用。我想出了添加另一个ExecutorService或者将线程池提升到更大的数字,比如256。但这感觉不太对。我还有其他选择吗

我现在很难想出正确的方法来做这件事

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

步骤摘要和问题的位置:

  • 下载图书信息
  • 下载网页:

    • 页面信息
    • 逐部分分页--死锁-线程数不足

      @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);
      }
      
      @覆盖
      公共图书getBook(int bookId){
      Book Book=books.get(bookId);
      if(book==null){
      HttpURLConnection conn=factory.getBook(bookId);
      字符串s=读取(连接);
      书籍=解释。阅读书籍;
      books.put(book.getId(),book);
      }
      还书;
      }
      @凌驾
      公共页面getPage(int bookId,int pageNum){
      字符串s=read(factory.getPage(bookId,pageNum));
      列表eIds=解释.readExercises;
      列表练习=新的ArrayList(eIds.size());
      CountDownLatch latch=新的CountDownLatch(eIds.size());
      System.out.println(“D:请求dl页面”+bookId+'>'+pageNum);
      for(int-eId:eIds){
      System.out.println(“eId”+eId);
      服务提交(()->{
      试一试{
      //由于缺少可用线程,这里的代码无法执行
      System.out.println(“D:请求dl exer”+eId);
      字符串sE=read(factory.getExercise(bookId,eId));
      练习=口译。阅读练习(sE);
      增加(练习);
      倒计时();
      }捕获(例外e){
      e、 printStackTrace();
      }
      });
      }
      试一试{
      satch.wait();
      }捕捉(中断异常e){
      e、 printStackTrace();
      }
      返回新页面(pageNum,练习);
      }
      @凌驾
      公共整本书Get整本书(int bookId){
      Book Book=getBook(bookId);
      列表页面=新的ArrayList(book.getPages().size());
      CountDownLatch latch=新的CountDownLatch(book.getPages().size());
      System.out.println(“D:请求dl book”+bookId);
      for(int pageNum:book.getPages()){
      服务提交(()->{
      试一试{
      Page=getPage(bookId,pageNum);
      System.out.println(“获取页面:+页面);
      页面。添加(第页);
      倒计时();
      }捕获(例外e){
      e、 printStackTrace();
      }
      });
      }
      试一试{
      System.out.println(“等待书本”+bookId);
      satch.wait();
      }捕捉(中断异常e){
      e、 printStackTrace();
      return null;//最好返回null而不是损坏的数据
      }
      返回新的整本书(书、页);
      }
      
  • 输出的结尾是:
    
    D:请求dl第10753页>第67页
    eId235082
    eId235092
    之后,它停止(从技术上讲,它正在运行,但什么也不做)


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

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

    @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);
    }
    
    看起来您的线程正在执行大量I/O工作(这很好),但如果您不关闭这些连接,则可能是任务未完成,线程池无法为其他任务重新分配线程


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

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

    @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);
    }
    
    看起来您的线程正在执行大量I/O工作(这很好),但如果您不关闭这些连接,则可能是任务未完成,线程池无法为其他任务重新分配线程


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

    由于您正在执行两种不同类型的任务,而第二种任务是第一种任务的子任务,因此执行器将充满第一种任务,而这些任务无法完成,因为它们的子任务无法执行。虽然这不是一个典型的死锁示例,但我认为它符合条件


    我处理这个问题的方法是在
    getPage()
    中删除executor的用法。如果出于某种原因(尽管我看不到任何有效的原因),您希望/需要使用多个线程来保持
    getPage()
    ,那么您必须提供一个单独的
    执行器供其使用,这样子任务将始终有机会完成。

    因为您正在执行两种不同的任务,第二个任务是第一个任务的子任务,最终执行者被第一个任务填满,这些任务无法完成,因为它们的子任务无法执行。虽然这不是一个典型的死锁示例,但我认为它符合条件

    我处理这个问题的方法是在
    getPage()
    中删除executor的用法。如果出于某种原因(尽管我没有看到任何