线程莫名其妙地死去

时间:2013-06-05 06:03:45

标签: java threadpool

我有一个最近开始使用100%CPU时间的Java进程。使用jdb我发现这是由ThreadPoolExecutor重复创建线程引起的。

有问题的执行人被定义为:

private final ScheduledExecutorService _scheduler = Executors.newScheduledThreadPool(0, new NamedThreadFactory("OrderServiceScheduler", true, null));

计划的唯一任务是:

_scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() {  s_log.info("Resetting order books"); _liveOrderBook.clear(); } }, 
                               midnightTodayInMs, 
                               TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS), 
                               TimeUnit.MILLISECONDS);

永远不会打印日志语句(我99%确定为此记录器启用了INFO级别日志记录)。

我首先在jdb中运行trace go methods并看到很多行:

Method entered: "thread=OrderServiceScheduler-thread-22237794", com.kbcfp.util.NamedThreadFactory.newThread(), line=45 bci=0
Method entered: "thread=OrderServiceScheduler-thread-22237794", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=34 bci=0
Method exited: return value = null, "thread=OrderServiceScheduler-thread-22237794", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=38 bci=15
Method exited: return value = instance of java.lang.Thread(name='OrderServiceScheduler-thread-22237795', id=6388), "thread=OrderServiceScheduler-thread-22237794", com.kbcfp.util.NamedThreadFactory.newThread(), line=52 bci=68

所以我在org.apache.log4j.helpers.ThreadLocalMap.childValue中设置了一个断点并继续进行,直到线程意外停止执行(紧跟在最后next, thread OrderServiceScheduler-thread-151389734 isn't listed in the output of线程之后):

> stop in org.apache.log4j.helpers.ThreadLocalMap.childValue
Set breakpoint org.apache.log4j.helpers.ThreadLocalMap.childValue
>
Breakpoint hit: "thread=OrderServiceScheduler-thread-151389734", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=34 bci=0

OrderServiceScheduler-thread-151389734[1] clear org.apache.log4j.helpers.ThreadLocalMap.childValue
Removed: breakpoint org.apache.log4j.helpers.ThreadLocalMap.childValue
OrderServiceScheduler-thread-151389734[1] where
  [1] org.apache.log4j.helpers.ThreadLocalMap.childValue (ThreadLocalMap.java:34)
  [2] java.lang.ThreadLocal$ThreadLocalMap.<init> (ThreadLocal.java:353)
  [3] java.lang.ThreadLocal$ThreadLocalMap.<init> (ThreadLocal.java:261)
  [4] java.lang.ThreadLocal.createInheritedMap (ThreadLocal.java:236)
  [5] java.lang.Thread.init (Thread.java:401)
  [6] java.lang.Thread.<init> (Thread.java:652)
  [7] com.kbcfp.util.NamedThreadFactory.newThread (NamedThreadFactory.java:45)
  [8] java.util.concurrent.ThreadPoolExecutor$Worker.<init> (ThreadPoolExecutor.java:598)
  [9] java.util.concurrent.ThreadPoolExecutor.addWorker (ThreadPoolExecutor.java:913)
  [10] java.util.concurrent.ThreadPoolExecutor.processWorkerExit (ThreadPoolExecutor.java:992)
  [11] java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1,128)
  [12] java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:603)
  [13] java.lang.Thread.run (Thread.java:722)
OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=35 bci=5

OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", org.apache.log4j.helpers.ThreadLocalMap.childValue(), line=38 bci=14

OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", com.kbcfp.util.NamedThreadFactory.newThread(), line=45 bci=40

OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", com.kbcfp.util.NamedThreadFactory.newThread(), line=48 bci=41

OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", com.kbcfp.util.NamedThreadFactory.newThread(), line=49 bci=49

OrderServiceScheduler-thread-151389734[1] next
>
Step completed: "thread=OrderServiceScheduler-thread-151389734", com.kbcfp.util.NamedThreadFactory.newThread(), line=52 bci=67

OrderServiceScheduler-thread-151389734[1] next
>

我们的NamedThreadFactory课程没有做任何令人兴奋的事情:

  44: public Thread newThread(Runnable r) {
  45:   Thread t = new Thread(_group, r,
  46:       _namePrefix + _threadNumber.getAndIncrement(),
  47:       0);
  48:   t.setDaemon(_makeDaemon);
  49:   if(_overridePriority != null) {
  50:     t.setPriority(_overridePriority);
  51:   }
  52:   return t;
  53: }

据我所知,下一行执行应该是ThreadPoolExecutor的914,但线程会死掉。为什么呢?

作为参考,这是在Solaris x86主机上的JDK 1.7.0_07上运行的。

我所做的唯一改变是添加了ActiveMQ的客户端库。我怀疑这与在类路径中添加slf4j(特别是slf4j-api-1.6.6.jarslf4j-log4j12-1.6.6.jar)有关,但我无法证明这一点。

更新

我已将执行的任务更改为单独的类:

public class TestingFoo implements Runnable 
{
  private final Logger s_log;

  public TestingFoo(final Logger log)
  {
    s_log = log;
  }

  @Override 
  public void run() 
  {  
    try
    {
      s_log.info("Resetting order books"); 
  //    _liveOrderBook.clear(); 
    }
    catch (final Throwable t)
    {
      t.printStackTrace();
    }
  }
}

我在jdb行设置了一个log.info断点并且没有被击中。另外,我没有看到在stderr(或log4j文件)上打印堆栈跟踪。另外,我在安排任务之前放了另一条s_log.info行, 打印出来。

我现在正在下载JDK 1.7.0_21以查看是否会产生影响。

更新2

这是由于将核心池大小设置为零,因为Peter Lawrey谈到了他的回答。但是,它不会导致无法运行,而是导致ThreadPoolExecutor的{​​{1}}方法立即退出。该方法的要点如下:

runWorker

设置核心池大小会导致runWorker(Worker) { ... while (task != null || (task = getTask()) != null) { ... task.run(); ... } processWorkerExit(..); } 返回getTask而不会阻止要提交的任务。这导致循环退出,然后调用null,这将创建另一个线程来替换现有的线程。

我相信我在调试器中看到的问题是由于JIT的代码。我在线程工厂中放入一个processWorkerExit语句来减慢线程的创建速度,这意味着在应用任何优化之前我有时间附加调试器。

TL; DR:我是个白痴。

更新3

对于未来可能会遇到这种情况的任何人......还有另一个similar question on SO提到这种行为在Java 7中发生了变化。在改变之前,核心大小为零,就像Peter Lawrey建议的那样,导致没有线程被创建(bug report)。放入change以确保创建一个线程,这似乎会导致重复创建线程。这可以使用与错误报告中的测试用例非常相似的代码进行复制:

Thread.sleep

1 个答案:

答案 0 :(得分:2)

我误解了这个问题。你是建议线程本身被杀死并重新启动?您将核心大小设置为0,这不太可能达到您想要的效果。我希望它不会创建一个线程,在这种情况下你的线程永远不会运行,或者它会为每个任务创建一个线程(我怀疑不会)。

我根本不会设置核心大小而只使用Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory)使用核心大小很少会按照我的经验完全按照您的意愿执行;)

如果这是问题,它应该在本地PC上执行完全相同的操作,您不需要进行远程调试。


您很可能会抛出一个被丢弃的错误。这很容易完成,是线程默默死亡的常见情况。我建议你遵循这种模式。

new Runnable() { 
    @Override public void run() {
        try {
            run0();
        } catch(Throwable t) { // this will catch everything not just Exceptions.
            // log t or print it
        }
    }
    void run0() {
        s_log.info("Resetting order books");
        _liveOrderBook.clear(); 
    } 
}

您可以使用final run()方法定义AbstractRunnable以将其包装起来。只有在您不轮询Future返回以检查故障时才需要它。 (我怀疑你丢弃了这个对象)