ExecutorService的奇怪行为

时间:2011-11-03 04:30:51

标签: java multithreading concurrency java.util.concurrent

我在Executors.newFixedThreadPool(8)创建的8个ExecutorService线程中执行了5000个类似的Callable任务。每个任务都进入数据库以检索要处理的大量数据。

一切正常,99%的时间,但有时我看到在日志文件中一个很奇怪的执行日志信息,当DB缓慢或卡住(不要问为什么)和8个正在运行的任务停滞不前,尚未完成的所有8个线程,ExecutorService开始提交更多任务来逐个执行!

因此,日志显示在某些时刻ExecutorService变得疯狂并开始在等待队列中调用越来越多任务的Callable的call()方法,而无需等待先前的任务完成。越来越多的任务向DB发送请求,这最终使DB陷入困境,Java堆内存耗尽。

看起来在ExecutorService中发生了一些奇怪的事情,或者我对情况的理解是错误的。有没有人见过这样的东西?

我的脑堆溢出

P.S。这是来自Java API的引用:

  

Executors.newFixedThreadPool(int nThreads)

     

创建一个重用固定数量的线程的线程池   关闭共享的无界队列。在任何时候,最多nThreads线程   将是主动处理任务。如果提交了其他任务   当所有线程都处于活动状态时,它们将在队列中等待直到a   线程可用。 如果任何线程因故障而终止   在关闭之前执行期间,如果是,新的将取代它   需要执行后续任务

难道这实际上发生,我的任务导致线程死亡和ExecutorService的创建多个线程并提交新的8个任务给他们,他们死和ExecutorService的创建8个更多的线程,并提交更多的任务8?

ps.s.s。:Callable的call()内部的整个操作都被try catch包围,所以如果我的操作中发生任何异常,将捕获并记录异常。这一切都没有发生。调用该调用并且永远不会返回,而下一个任务是逐个调用的,并且永远不会返回,永远不会完成,也不会抛出任何异常。

我怀疑我的任务会导致线程池中的线程死掉。怎么可能模仿?

2 个答案:

答案 0 :(得分:3)

我也会猜测:

  1. 您提交了5000个涉及从数据库中提取数据的任务。
  2. 不久之后,您在所需的行/表上遇到严重的锁争用。也许外部进程正在获取写入的独占锁。也许有一个僵局。
  3. 一个接一个地,任务阻塞,等待共享/读取锁被授予。
  4. 好像所有8个线程都被暂停,等待I/O
  5. 不久之后,数据库/数据库驱动程序注意到任务等待共享锁的时间太长。它按顺序立即向任务分发Lock Wait Timeout个例外。
  6. 因此,一个接一个地,任务从队列中失败,等待任务被推送到执行中,只是再次失败。
  7. 请注意,任务中的例外不会停止ExecutorService。它只是将该任务标记为已完成并继续。

    见这个例子:

    public class Foo {
    
        static class Task implements Callable<String> {
            private static AtomicInteger i = new AtomicInteger(1);
    
            public String call() throws Exception {
                i.incrementAndGet();
                if (i.get() % 2 != 0) {
                    throw new RuntimeException("That's odd, I failed.");
                }
                return "I'm done";
            }
        }
    
        public static void main(String[] args) throws Exception {
            ExecutorService es = Executors.newFixedThreadPool(2);
            List<Future<String>> futures = new ArrayList<Future<String>>();
            for (int i = 0; i < 5; i++) {
                futures.add(es.submit(new Task()));
            }
            for (Future<String> future : futures) {
                try {
                    System.out.println(future.get());
                } catch (ExecutionException ee) {
                    System.err.println(ee.getCause());
                }
            }
            es.shutdown();
        }
    }
    

    可能的输出:

    I'm done
    I'm done
    I'm done
    java.lang.RuntimeException: That's odd, I failed.
    java.lang.RuntimeException: That's odd, I failed.
    

答案 1 :(得分:0)

这只是猜测,(我认为鉴于问题中代码不足,我认为这是值得的):

如果当前任务抛出异常,

ExecutorService.invokeAll(Collection<? extends Callable<T>> tasks)将继续执行其他任务。 (你使用的是invokeAll()吗?我认为submit(Callable<T> task)具有相同的行为,但是从javadoc中看不清楚)

在后续任务开始运行之前,您是否可以检查那些“卡住”任务变为Future.isDone()?潜在的异常被抛出而在日志中看不到......

来自javadoc:

  

请注意,已完成的任务可能已正常终止或通过   抛出异常。

http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ExecutorService.html#invokeAll(java.util.Collection%29

如果 这个案例,你可以抓住&amp;记录Callable.call()方法定义中的所有异常。

HTH