corePoolSize为0的ThreadPoolExecutor在任务队列已满之前不应执行任务

问题描述 投票:7回答:3

我正在经历Java Concurrency In Practice,并陷入了[[8.3.1线程创建和拆除主题。以下脚注警告有关将corePoolSize保持为零。

开发人员有时会尝试将核心大小设置为零,以便工作线程将最终被拆除,因此不会阻止JVM退出,但这可能会导致某些情况在工作池中不使用SynchronousQueue的线程池中出现奇怪行为(就像newCachedThreadPool一样)。

如果池已经达到核心大小,则ThreadPoolExecutor将创建仅当工作队列已满时才使用新线程。所以任务提交到带有工作队列的线程池具有任何容量且核心大小为零的文件,直到队列填满后才会执行

,通常不是想要的。因此,为了验证这一点,我编写了此程序,但如上所述,该程序无法正常工作。

final int corePoolSize = 0; ThreadPoolExecutor tp = new ThreadPoolExecutor(corePoolSize, 1, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); // If the pool is already at the core size if (tp.getPoolSize() == corePoolSize) { ExecutorService ex = tp; // So tasks submitted to a thread pool with a work queue that has any capacity // and a core size of zero will not execute until the queue fills up. // So, this should not execute until queue fills up. ex.execute(() -> System.out.println("Hello")); }

输出

你好因此,程序的行为是否建议如果提交任务而与ThreadPoolExecutor无关,corePoolSize=0至少创建一个线程。如果是,那么教科书中的警告是什么。

编辑:

根据@ S.K。的建议,对jdk1.5.0_22中的代码进行了以下更改:ThreadPoolExecutor tp = new ThreadPoolExecutor(corePoolSize, 1, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1));//Queue size is set to 1.

但进行此更改后,程序将终止而不打印任何输出。

所以我会从书中误解这些陈述吗?

EDIT(@sjlee):

很难在注释中添加代码,因此我将其添加为编辑内容...您可以尝试这种修改并针对最新的JDK和JDK运行它吗? 1.5?final int corePoolSize = 0; ThreadPoolExecutor tp = new ThreadPoolExecutor(corePoolSize, 1, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); // If the pool is already at the core size if (tp.getPoolSize() == corePoolSize) { ExecutorService ex = tp; // So tasks submitted to a thread pool with a work queue that has any capacity // and a core size of zero will not execute until the queue fills up. // So, this should not execute until queue fills up. ex.execute(() -> System.out.println("Hello")); } tp.shutdown(); if (tp.awaitTermination(1, TimeUnit.SECONDS)) { System.out.println("thread pool shut down. exiting."); } else { System.out.println("shutdown timed out. exiting."); }
@@ sjlee已在评论中发布了结果。
java multithreading concurrency threadpool java-threads
3个回答
3
投票
[在jdk 1.5、1.6、1.7和1.8中运行此程序时,我在1.5、1.6和1.7+中发现了ThreadPoolExecutor#execute(Runnable)的不同实现。这是我发现的内容:

[JDK 1.5实现

//Here poolSize is the number of core threads running. public void execute(Runnable command) { if (command == null) throw new NullPointerException(); for (;;) { if (runState != RUNNING) { reject(command); return; } if (poolSize < corePoolSize && addIfUnderCorePoolSize(command)) return; if (workQueue.offer(command)) return; Runnable r = addIfUnderMaximumPoolSize(command); if (r == command) return; if (r == null) { reject(command); return; } // else retry } }

corePoolSize为0时,此实现不会创建线程,因此提供的任务不会执行。

[JDK 1.6实现

//Here poolSize is the number of core threads running. public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) reject(command); // is shutdown or saturated } }

[DK0 1.6会创建一个新线程,即使corePoolSize为0。

JDK 1.7+实现

(类似于JDK 1.6,但具有更好的锁和状态检查) public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }

[JDK 1.7也创建了一个新线程,即使corePoolSize为0。

因此,似乎corePoolSize=0在JDK 1.5和JDK 1.6+的每个版本中都是特例。

但是奇怪的是,这本书的解释与任何程序结果都不匹配。


0
投票
似乎是较早的Java版本的错误,但Java 1.8中现在不存在。

根据ThreadPoolExecutor.execute()中的Java 1.8文档:

/* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * .... */

在第二点上,在将工作线程添加到队列后进行了重新检查,即如果不对任务进行排队,而是可以启动新线程,而不是回滚队列并启动新线程。

这是正在发生的事情。在第一次检查期间,任务已排队,但在重新检查期间,启动了一个新线程来执行您的任务。


0
投票
[Java 5中,当核心池为零时,ThreadPoolExecutor的奇怪行为显然被认为是Java 6中已悄悄修复的错误。

实际上,由于进行了一些重做,该问题在Java 7中再次出现。然后,它被确认为错误,并已修复。

参考:

© www.soinside.com 2019 - 2024. All rights reserved.