ExecutorService变慢,让我的电脑陷入困境

时间:2013-12-17 04:11:56

标签: java executorservice java.util.concurrent

我正在为网站编写解析器,它有很多页面(我称之为IndexPages)。每个页面都有很多链接(在IndexPage中大约有300到400个链接)。我使用Java的ExecutorService在一个IndexPage中同时调用12 Callables。每个Callable只是向一个链接发出一个http请求,并进行一些解析和数据库存储操作。当第一个IndexPage完成时,程序进入第二个IndexPage,直到找不到下一个IndexPage。

parsing 1st page 在运行时,似乎没问题,我可以很好地观察线程的工作/调度。每个链接的解析/存储只需要大约1到2秒。

After running 2 hours 但随着时间的推移,我观察到每个Callable(解析/存储)需要越来越长的时间。以这张照片为例,有时需要10秒或更长时间才能完成Callable(绿色条是RUNNING,紫色条是WAITING)。而我的电脑正在陷入困境,一切都变得迟钝。

这是我的主要算法:

ExecutorService executorService = Executors.newFixedThreadPool(12);    
String indexUrl = // Set initial (1st page) IndexPage
while(true)
{
  String nextPage = // parse next page in the indexUrl

  Set<Callable<Void>> callables = new HashSet<>();
  for(String url : getUrls(indexUrl))
  {
    Callable callable = new ParserCallable(url , … and some DAOs);
    callables.add(callable);
  } 

  try {
    executorService.invokeAll(callables);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }

  if (nextPage == null) 
    break;

  indexUrl = nextPage;
} // true
executorService.shutdown();

该算法简单且不言自明。我想知道是什么原因导致这种情况?无论如何要防止这种性能下降?

CPU/Memory CPU /内存/堆显示合理的使用情况。

Environments 环境,仅供参考。

====================更新====================

我已将我的实施从ExecutorService更改为ForkJoinPool

ForkJoinPool pool=new ForkJoinPool(12);
String indexUrl = // Set initial (1st page) IndexPage
while(true)
{
  Set<Callable<Void>> callables = new HashSet<>();
  for(String url : for(String url : getUrls(indexUrl)))
  {
    Callable callable = new ParserCallable(url , DAOs...);
    callables.add(callable);
  }
  pool.invokeAll(callables);

  String nextPage = // parse next page in this indexUrl
  if (nextPage == null)
    break;

  indexUrl = nextPage;
} // true

它需要比ExecutorService解决方案更长的时间。 ExecutorService大约需要2个小时才能完成所有页面,ForkJoinPool需要3个小时,而每个Callable仍然需要更长时间才能完成(从1秒到5,6甚至10秒)。我不介意需要更长的时间,我只希望完成一份工作需要不间断的时间(不长也不长)。

我想知道我是否在解析器中创建了很多(非线程安全的)GregorianCalendarDateSimpleDateFormat对象,并导致一些线程问题。但我没有重用这些对象或在线程之间传递它们。所以我仍然找不到原因。

3 个答案:

答案 0 :(得分:1)

基于堆,您有内存问题。 ExecutorService.invokeAll会将Callable个实例的所有结果收集到List中,并在List完成后返回ExecutorService.submit。您可能需要考虑简单地调用Callable,因为您似乎并不关心每个{{1}}的结果。

答案 1 :(得分:0)

我无法理解为什么需要Callable来解析索引页面,因为你的'Caller'方法不期望ParserCallable产生任何结果。我可以看到您需要对异常处理进行排序,但仍然可以使用Runnable进行管理。

当你使用Callable.call()时,它会返回FutureTask,这是永远不会使用的。

您应该能够通过使用Runnable来改进实现,这可以避免这种额外的操作

ExecutorService executor = Executors.newFixedThreadPool(12);
for(String url : getUrls(indexUrl))  {
  Runnable worker = new ParserRunnable(url , … and some DAOs);
  executor.execute(worker);
}

class ParserRunnable implements Runnable{
}

答案 2 :(得分:0)

据我了解,如果你有40个页面,每个页面有~300个URL,你将创建~12,000个Callables?虽然可能没有太多的Callables,但它有很多HTTP连接和数据库连接。

我认为你应该尝试每页使用一个Callable。你通过并行运行它们仍然可以获得很多。我不知道你在为HTTP请求使用了什么,但是你可以在那里重用系统资源,而不是打开和关闭它们中的12,000个。

特别是对于DB。你只有40个连接。您甚至可以通过在本地收集~300条记录,然后使用批量更新来提高效率。