当前位置: 首页 > 知识库问答 >
问题:

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

解飞语
2023-03-14

我在练习Java并发性时,被困在了8.3.1线程创建和拆卸主题上。下面的脚注警告要将corePoolSize保持为零。

开发人员有时会倾向于将核心大小设置为零,这样工作线程最终会被拆除,因此不会阻止JVM退出,但这可能会在不使用同步队列的线程池中导致一些看似奇怪的行为。工作队列(如newCachedThreadPool所做的那样)。如果池已经达到核心大小,则ThreadPoolExector仅在工作队列已满时才会创建新线程。因此,提交给具有任何容量且核心大小为零的工作队列的线程池的任务将不会执行,直到队列填满,这通常不是我们想要的。

所以为了验证这一点,我写了这个程序,它不能如上所述工作。

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

输出:您好

那么,程序的行为是否表明,如果提交了一个任务,无论corePoolSize=0,ThreadPoolExecutor都会创建至少一个线程。如果是,那么教科书中的警告是什么。

编辑:测试了jdk1.5中的代码。0_22@S. K.的建议,并进行以下更改:

ThreadPoolExecutor tp = new ThreadPoolExecutor(corePoolSize, 1, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(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.");
}

@sjlee已经在评论中发布了结果。

共有3个答案

涂羽
2023-03-14

似乎这是旧java版本的一个错误,但它现在在Java1.8中不存在。

根据ThreadPoolExecutor.execute()的Java1.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.
     * ....
     */

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

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

章乐逸
2023-03-14

在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
    }
}

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

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

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

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

轩辕经国
2023-03-14

当核心池大小为零时,Java 5中ThreadPoolExecutor的这种奇怪行为显然被认为是一个bug,并在Java 6中悄悄地修复了。

事实上,这个问题在Java 7中再次出现,这是在6和7之间进行一些代码修改的结果。然后报告为bug,确认为bug并修复。

无论哪种方式,您都不应使用受此错误影响的Java版本。Java5在2015年已终止使用,Java6及更高版本的最新可用版本不受影响。“实践中的Java并发”这一部分不再合适。

参考文献:

  • http://cs.oswego.edu/pipermail/concurrency-interest/2006-December/003453.html(读取整个线程)
  • http://gee.cs.oswego.edu/dl/concurrency-interest/index.html(请参阅JSR166y捆绑包中ThreadPoolExecator的版本。)
  • https://bugs.openjdk.java.net/browse/JDK-7091003)
 类似资料:
  • 问题内容: 我正在学习 Java Concurrency in Practice, 并陷入了 8.3.1线程创建和拆除的 主题。以下脚注警告要保持为零。 有时,开发人员倾向于将核心大小设置为零,以使工作线程最终被拆除,因此不会阻止JVM退出,但这会在不使用a的线程池中引起一些奇怪的现象。他们的工作队列使用SynchronousQueue(就像newCachedThreadPool一样)。 如果池已

  • 问题内容: 我对ThreadPoolExecutor有一个非常简单的问题。我有以下情况:我必须使用队列中的对象,为它们创建适当的工作程序任务,然后将其提交给ThreadPoolExecutor。这很简单。但是在关闭情况下, 许多 工作人员可能会排队等待执行。由于这些任务之一可能正在运行一个小时,而且我希望相对快速地正常关闭应用程序,因此我想从ThreadPoolExecutor中丢弃所有排队的任务

  • 我已经创建了3个任务。Task3取决于Task1和Task2的结果。在调试代码时,它会正确执行,但在运行应用程序时,Task3会在Task1和Task2完成之前执行。 示例代码: 提前谢谢。

  • 我找不到任何符合我要求的遗嘱执行人。我想要一个具有corePoolSize、maximumPoolSize和BlockingQueue的ExecutorService; 当执行函数被调用时,像往常一样,使用核心线程,如果核心线程正在使用,则将任务放入队列,如果队列已满,则创建新线程,直到达到最大池大小。这是ThreaPoolExecator的标准行为。线程池执行器 在这部分之前一切都好。我可以使用

  • 我正在尝试创建一个应用程序,用于查询cat图像的站点,如果JSON ID是唯一的,则将其保存到android设备,然后从设备以幻灯片格式显示它们。尽管如此,我的AsyncTask似乎并没有实际执行。调试器确认已建立网络连接,并且不会向我反馈任何错误,所以我不知道代码出了什么问题。希望有人能帮忙!代码如下: