Java 即使executor关闭并取消,程序也不会停止运行

Java 即使executor关闭并取消,程序也不会停止运行,java,java.util.concurrent,java-threads,Java,Java.util.concurrent,Java Threads,我编写了一个小代码,用于练习执行器和线程。它包括以下内容: 使用无限队列创建大小为3的固定线程池。 提交3个无限循环的任务,同时向池中提交 所有线程都已占用 提交第四个任务,该任务将在队列中等待。 executor.shutdown并执行println以查看如何生成活动任务和任务计数。 将标志设置为false以停止无限,然后执行 println用于查看如何生成活动任务和任务计数 使用MayInterruptFrunning=true取消所有期货,然后 做一个println,看看如何使活动任务和任

我编写了一个小代码,用于练习执行器和线程。它包括以下内容:

使用无限队列创建大小为3的固定线程池。 提交3个无限循环的任务,同时向池中提交 所有线程都已占用 提交第四个任务,该任务将在队列中等待。 executor.shutdown并执行println以查看如何生成活动任务和任务计数。 将标志设置为false以停止无限,然后执行 println用于查看如何生成活动任务和任务计数 使用MayInterruptFrunning=true取消所有期货,然后 做一个println,看看如何使活动任务和任务计数 有 代码如下:

public class Main {


private static ThreadPoolExecutor fixexThreadPool;

public static void main(String[] args) throws InterruptedException {
    System.out.println("Creating fixed thread pool of size 3 and infinite queue.");
    fixexThreadPool = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    final Boolean[] flag = new Boolean[1];
    flag[0] = true;
    List<Future> futures = new ArrayList<>();

    System.out.println("Submiting 3 threads");
    for (int i = 0; i < 3; i++) {
        futures.add(fixexThreadPool.submit(() -> {
            int a = 1;
            while (flag[0]) {
                a++;
            }
            System.out.println("Finishing thread execution.");
        }));
    }
    System.out.println("Done submiting 3 threads.");
    System.out.println(String.format("Active count: %s | Completed task count: %s | task count: %s", fixexThreadPool.getActiveCount(), fixexThreadPool.getCompletedTaskCount(), fixexThreadPool.getTaskCount()));
    Thread.sleep(3000L);
    System.out.println("Submitting a 4th thread.");

    futures.add(fixexThreadPool.submit(() -> {
        int a = 1;
        while (flag[0]) {
            a++;
        }
        System.out.println("Finishing thread execution");
    }));

    System.out.println(String.format("Active count: %s | Completed task count: %s | task count: %s", fixexThreadPool.getActiveCount(), fixexThreadPool.getCompletedTaskCount(), fixexThreadPool.getTaskCount()));

    System.out.println("Executor shutdown");
    fixexThreadPool.shutdown();
    System.out.println(String.format("Active count: %s | Completed task count: %s | task count: %s", fixexThreadPool.getActiveCount(), fixexThreadPool.getCompletedTaskCount(), fixexThreadPool.getTaskCount()));
    Thread.sleep(2000L);
    System.out.println("Setting flag to false.");
    flag[0] = false;
    Thread.sleep(2000L);
    System.out.println(String.format("Active count: %s | Completed task count: %s | task count: %s", fixexThreadPool.getActiveCount(), fixexThreadPool.getCompletedTaskCount(), fixexThreadPool.getTaskCount()));
    System.out.println("Cancelling all futures.");
    futures.forEach(f -> f.cancel(true));
    System.out.println(String.format("Active count: %s | Completed task count: %s | task count: %s", fixexThreadPool.getActiveCount(), fixexThreadPool.getCompletedTaskCount(), fixexThreadPool.getTaskCount()));
}
}

这是执行的输出:

创建大小为3的固定线程池和无限队列。 提交3个线程 完成提交3个线程。 活动计数:3 |已完成任务计数:0 |任务计数:3 提交第四个线程。 活动计数:3 |已完成任务计数:0 |任务计数:4 执行器关闭 活动计数:3 |已完成任务计数:0 |任务计数:4 将标志设置为false。 活动计数:3 |已完成任务计数:0 |任务计数:4 取消所有期货交易。 活动计数:3 |已完成任务计数:0 |任务计数:4 有几件事我不明白

为什么在关闭executor之后,仍然有活动线程? 为什么,在将标志更改为false以中断无限循环后,无限while不会中断? 为什么每次取消以后都会有活动线程? 无论是否将标志更改为false、关闭executor甚至取消所有期货,我的程序都不会停止运行。为什么呢?
提前谢谢

这个问题可以通过使用volatile关键字来解决。提供了大量的答案,详细解释了什么是volatile,还有大量的教程/资源/博客可以提供更多的细节。关于volatile的另一个超级详细的线程

老实说,互联网上有很多人可以比我更好、更准确地解释它,但简言之,volatile是一个Java修饰符,当您有一个由多个线程共享的资源时,应该使用它。它告诉JVM确保每个线程缓存的值与主内存中的值同步

显然,JVM在某个地方出现了问题,线程持有的值与实际值不太匹配。对您的实现进行一个小的更改可以解决此问题:

将标志设为类实例变量

private volatile static Boolean[] flag = new Boolean[1];
看看我为什么这么做

因此,让我们从更大的角度来看:

private static ThreadPoolExecutor fixexThreadPool;
private volatile static Boolean[] flag = new Boolean[1];

public static void main(String[] args) throws InterruptedException {
    System.out.println("Creating fixed thread pool of size 3 and infinite queue.");
    fixexThreadPool = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    flag[0] = true;
    List<Future> futures = new ArrayList<>();

    System.out.println("Submiting 3 threads");
    ...
将其放入您的代码中,我们可以得到:

    Log();
    System.out.println("Executor shutdown");
    fixexThreadPool.shutdown();
    Log();
    Thread.sleep(2000L);
    System.out.println("Setting flag to false.");
    flag[0] = false;
    Thread.sleep(2000L);
    Log();
    System.out.println("Cancelling all futures.");
    futures.forEach(f -> System.out.println(String.format("Future cancelled - %s", f.cancel(true))));
    Log();
我还想在应用程序中添加一个快速心跳,时不时打印一条消息,这样我就可以看到幕后仍在运行/未运行的内容:

private static void Heartbeat(int a) {
    int amod = a % 1000000000;
    if(amod == 0) {
        System.out.println(a);
    }
}
使用中:

while (flag[0]) {
    a++;
    Heartbeat(a);
}
然后,我运行了一些测试运行,以不同的组合尝试了一些不同的东西:

注释掉fixexThreadPool.shutdown; 注释掉futures.forEachf->f.canceltrue; 注释输出标志[0]=false; 尝试使用/不使用新的volatile修复程序。 为了让一个已经很长的答案再短一点,你可以自己探索这些组合,但我发现

如果没有volatile关键字,线程就会陷入终止状态 状态我只能假设关机不会停止 由于这些任务已经提交,所以没有线程思考,所以循环执行他们正在执行的任务 标志[0]==false,它们将继续递增a

据我所见,这展示了文档中概述的行为。因此,shutdown并不是停止while循环,它只是停止任何新的future被提交到线程池并被添加到阻塞队列,然后耐心地等待while循环完成,显然,如果标志上没有volatile修饰符,它们永远不会完成

使用volatile,但是注释掉关机,显然,每个任务 终止控制台日志4,完成线程执行。但是 线程池保持活动状态,程序本身不会终止。池正在耐心地等待新的任务,而它显然永远不会得到这些任务。 “future.cancel”的逻辑目前有点超出我的理解。我运行了一个名为just future.cancel的测试,并做了一些更深入的日志记录/调查,但没有真正的具体答案

我的运行理论是future已经添加到线程池中的阻塞队列中,因此调用cancel不会影响线程池的执行,因此有效地单独调用future.cancel对fixexThreadPool没有任何影响

希望这能提供足够的思考:

为什么在关闭executor之后,仍然有活动线程

通过调用shutdown,我们请求。线程池停止接受新任务,但 等待已提交的任务完成-正在运行或排队。因此,shutdown方法不会尝试停止线程。从概念上讲,ExecutorService实现将任务提交与任务执行分开。换句话说,我们拥有任务,但不拥有线程

为什么,在将标志更改为false之后,为了打破无限 循环,无限而不断裂

我们在这里用的是一个。但是根据Java内存模型,要让多个线程看到对共享数据标志所做的更改,我们必须采用同步或使用volatile关键字。因此,虽然同步提供互斥和内存可见性,但volatile关键字只提供内存可见性。因为这里只有主线程修改flag变量,所以将flag定义为静态volatile变量将使其按预期工作

public class Main {


    private static ThreadPoolExecutor fixexThreadPool;
    private static volatile Boolean[] flag = new Boolean[1];
    // the main method
}
为什么每次取消以后都会有活动线程

默认情况下,ThreadPoolExecutor使用FutureTask类作为将来的实现。FutureTask通过中断工作线程来取消。因此,可以期望停止任务,甚至终止线程。但是线程中断是一种协作机制。首先,任务必须对中断做出响应。它必须使用Thread.isInterrupted检查中断标志,如果可能,请退出。其次,线程所有者(在我们的例子中是线程池)必须检查中断状态并采取相应的行动。在本例中,任务对中断没有响应。它使用一个标志来取消其操作。让我们继续讨论线程所有者。ThreadPoolExecutor不会对中断状态进行操作,因此不会终止线程

无论是否将标志更改为false、shutdown executor甚至 取消所有未来,我的程序不会停止运行。为什么呢

如上所述,当前任务使用取消标志方法。因此,使用volatile关键字必须解决这个问题。然后任务将按预期停止。另一方面,取消不会对当前任务产生任何影响。因为它没有检查中断状态。我们可以让它像这样响应:

while (flag[0] && !Thread.currentThread().isInterrupted())

通过这种方式,任务还支持通过线程中断进行取消。

尝试阅读javadocs for shutdown和shutdownNow,并了解它们的区别。此外,由于线程大量循环,它们将消耗最大cpu。其他指令可能无法执行。hi@ScaryWombat,我已更改为Shutdownow,程序仍在运行。唯一更改的是第4个排队任务已被删除。我使用Boolean[],因为在使用lambda中的局部变量时,lambda假设它们是有效的最终变量,如果我直接使用Boolean,我将使用布尔值的副本,如果我从外部更改值,lambda将不会注意到更改。对吗?感谢@Ben给出的明确答案。我已将标志更改为volatile,现在线程正在完成:。然而,我仍然不明白为什么当我说executor.shutdown和我取消所有未来时程序没有完成…我猜如果在野生循环中有一个小睡眠,那么其他线程可能有机会执行。谢谢@Ben的解释。正如你所说,有些事情还不清楚。谢谢你的回答。超级清晰。你知道有哪本书或课程可以让我学到这些关于java线程和执行器的深层次的概念吗?我很高兴它对我有所帮助。Brian Goetz和他的朋友们在Java并发实践中详细介绍了这些主题。
while (flag[0] && !Thread.currentThread().isInterrupted())