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