具有明显空闲线程的Java-ExecutorService

具有明显空闲线程的Java-ExecutorService,java,multithreading,concurrency,threadpool,executorservice,Java,Multithreading,Concurrency,Threadpool,Executorservice,我观察到一种行为,我无法向执行人解释。我有一个应用程序在内存中加载了大约800人的个人资料。考虑下面的 ExecutorService es = Executors.newFixedThreadPool(8); // 8 cores machine Runnable enricherService = new Runnable() { @Override public void run() { doEnrichment(conn, tmpCachePer

我观察到一种行为,我无法向执行人解释。我有一个应用程序在内存中加载了大约800人的个人资料。考虑下面的

ExecutorService es = Executors.newFixedThreadPool(8); // 8 cores machine
Runnable enricherService = new Runnable() {

    @Override
    public void run() {
            doEnrichment(conn, tmpCachePerson);
    }
};

// tmpCachePerson is a ConcurrentLinkedQueue<Person>
while (tmpCachePerson.isEmpty() == false) {
    es.execute(enricherService);
}

es.shutdown();

try {
    while (!es.awaitTermination(24L, TimeUnit.HOURS)) {
        System.out.println("Waiting for termination");
    }
} catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
另一方面,这件作品速度非常快,只使用一个线程,在控制台中永不停止打印,并在3秒内完成

如果我将第一部分中的固定池替换为缓存池,那么在生成800个线程后,任务将在3秒钟内完成。如果在固定池中放置800个线程,则速度相同。任何人都能理解为什么固定池经常暂停,以及为什么它的速度不超过1个线程。下面是我看到的8个线程的摘录。如果您查看thread-1,它会在一个简单的getter上暂停5秒。在日志的其他部分,整个任务耗时约250ms

2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting executing SQL
2016-11-15 15:54:04.212 - pool-1-thread-1 - Done executing SQL in 0 ms
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting adding
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting Getting Val
2016-11-15 15:54:04.212 - pool-1-thread-1 - Done Getting Val in 0 ms
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting Getting Root
2016-11-15 15:54:04.212 - pool-1-thread-1 - Done Getting Root in 0 ms
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting Getting Path
2016-11-15 15:54:04.212 - pool-1-thread-1 - Done Getting Path in 0 ms
2016-11-15 15:54:04.212 - pool-1-thread-6 - Starting
2016-11-15 15:54:04.212 - pool-1-thread-8 - Starting  <-------------- All threads stop
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting executing SQL
2016-11-15 15:54:09.533 - pool-1-thread-6 - Starting executing SQL
2016-11-15 15:54:09.533 - pool-1-thread-8 - Done executing SQL in 0 ms
2016-11-15 15:54:09.533 - pool-1-thread-1 - Starting Getting Full Path
2016-11-15 15:54:09.533 - pool-1-thread-6 - Done executing SQL in 5320 ms
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting adding
2016-11-15 15:54:09.533 - pool-1-thread-6 - Starting adding
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting Getting Val
2016-11-15 15:54:09.533 - pool-1-thread-6 - Starting Getting Val
2016-11-15 15:54:09.533 - pool-1-thread-8 - Done Getting Val in 0 ms
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting Getting Root
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done Getting Full Path in 5320 ms
2016-11-15 15:54:09.533 - pool-1-thread-1 - Starting Adding Image
2016-11-15 15:54:09.533 - pool-1-thread-8 - Done Getting Root in 0 ms
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done Adding Image in 0 ms
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting Getting Path
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done adding in 5321 ms
2016-11-15 15:54:09.533 - pool-1-thread-1 - Starting setting
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done setting in 0 ms
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done in 5321 ms
2016-11-15 15:54:04.212-pool-1-thread-1-启动
2016-11-15 15:54:04.212-池-1-线程-1-开始执行SQL
2016-11-15 15:54:04.212-pool-1-thread-1-在0毫秒内执行完SQL
2016-11-15 15:54:04.212-池-1-线程-1-开始添加
2016-11-15 15:54:04.212-池-1-线程-1-开始获取Val
2016-11-15 15:54:04.212-池-1-线程-1-完成在0毫秒内获取Val
2016-11-15 15:54:04.212-池-1-线程-1-开始获取根
2016-11-15 15:54:04.212-pool-1-thread-1-在0毫秒内完成根目录获取
2016-11-15 15:54:04.212-池-1-线程-1-开始获取路径
2016-11-15 15:54:04.212-pool-1-thread-1-在0毫秒内完成获取路径
2016-11-15 15:54:04.212-pool-1-thread-6-启动

2016-11-15 15:54:04.212-pool-1-thread-8-开始这可能不是原因,但你有一个比赛条件:

while (tmpCachePerson.isEmpty() == false) {
    es.execute(enricherService);
}
绝对不能保证上下文切换发生;即使是这样,也可能比你预期的要慢;在工人启动之前,该循环可能会运行一百万次。在解决此问题之前,不要再进一步查看;这需要大量的旋转和内存开销

更好的模式是将轮询放在worker中:

Runnable enricherService = new Runnable() {
    @Override
    public void run() {
        while (!tmpCachePerson.isEmpty()) {
            doEnrichment(conn, tmpCachePerson);
            // TODO: error handling? Should a failure in doEnrichment kill the worker?
        }
    }
};
然后让工人们开始工作

for (int i = 0; i < 8; ++i) {
    es.execute(enricherService);
}
for(int i=0;i<8;++i){
执行(enricherService);
}

您是否使用关机和等待终止来确定何时完成doEnrichment运行?如果是这样的话,使用futures并调用get(长超时,TimeUnit)。是的,很好的建议。我将在代码投入生产时完成这项工作。这大约需要4.5秒。比一个线程多一秒钟,但比我拥有的要好。不是我想要的解决方案,而是越来越近了。谢谢你正在查询数据库。你是如何获得联系的?如果没有合并,您可能会看到一些性能问题。如果池太小(这听起来像是所有线程都使用单一连接,或者DB是瓶颈,但可能是连接。
doEnrichment(conn,tmpCachePerson)
甚至意味着单一连接。在Runnable中打开一个新连接(不要忘了在最后一次关闭它)。我以前有过你的建议,也怀疑是DB(SQLite),但是无论有没有共享连接,性能都是一样的。也就是说,我多次重新运行了你早期的建议,平均来说,它比单个线程快约2.8秒。所以我认为你第一次就获得了a。
Runnable enricherService = new Runnable() {
    @Override
    public void run() {
        while (!tmpCachePerson.isEmpty()) {
            doEnrichment(conn, tmpCachePerson);
            // TODO: error handling? Should a failure in doEnrichment kill the worker?
        }
    }
};
for (int i = 0; i < 8; ++i) {
    es.execute(enricherService);
}