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

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

开发人员有时会试图将核心大小设置为零,这样工作线程最终会被拆除,因此不会阻止JVM退出,但这可能会导致在不使用a的线程池中出现一些看似奇怪的行为。用于工作队列的SynchronousQueue(如newCachedThreadPool所做的那样)。 如果池已经是核心大小,则ThreadPoolExecutor仅在工作队列已满时才创建新线程。 因此,在队列填满之前提交给具有任何容量和核心大小为零的工作队列的线程池的任务将不会执行 ,这通常不是所希望的。

所以为了validation这一点,我写了这个程序,它不能像上面说的那样工作。

  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 。 如果是,那么教科书中的警告是什么。

编辑:在@ SK的建议下测试了jdk1.5.0_22中的代码,并进行了以下更改:

 ThreadPoolExecutor tp = new ThreadPoolExecutor(corePoolSize, 1, 5, TimeUnit.SECONDS, new LinkedBlockingQueue(1));//Queue size is set to 1. 

但是,通过此更改,程序终止而不打印任何输出。

我是否误解了书中的这些陈述?

编辑(@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."); } 

在jdk 1.5,1.6,1.7和1.8中运行该程序时,我在1.5,1.6和1.7+中发现了ThreadPoolExecutor#execute(Runnable) #cute 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 } } 

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

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); } 

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

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

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

似乎它是旧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. * .... */ 

在第二点中,在向队列添加工作程序之后有一个重新检查,如果不是对任务进行排队,则可以启动新线程,而不是回滚入队并启动新线程。

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