我目前无法找出执行此操作的正确方法。
我有一个固定线程池为 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); }
输出的结尾是:
D:请求dl页面10753>67
电子 ID235082
eId235092
之后它停止(技术上正在运行,但不执行任何操作)
当我中断线程(使用调试器)时,堆栈跟踪指向#getPage,更准确地说是latch.await()
。
最佳答案
由于您正在执行两种不同类型的任务,而第二个任务是第一个任务的子任务,因此执行器最终会执行第一个任务,而第一个任务无法完成,因为它们的子任务无法完成执行。虽然这不是典型的死锁示例,但我认为它符合条件。
我处理这个问题的方法是删除 getPage()
中执行程序的使用。如果出于某种原因(尽管我没有看到任何有效的原因)您希望/需要使用多个线程保留 getPage()
,则必须提供单独的 Executor
供其使用,因此子任务始终有机会完成。
关于java - 需要线程的方法导致的死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40870279/