main方法中启动了一个线程池,运行结束后jvm没有退出?撸完线程池源码我get到了这个知识点

429 阅读2分钟

问题

某天,写了一段很简单的多线程测试代码,如下

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ThreadPoolExecutor executorService = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));

        long start = System.currentTimeMillis();
        Callable<String> callable1 = () -> {
            Thread.sleep(1000);
            return "task1";
        };
        Callable<String> callable2 = () -> {
            Thread.sleep(2000);
            return "task2";
        };

        Future<String> future1 = executorService.submit(callable1);
        Future<String> future2 = executorService.submit(callable2);

        System.out.println(future1.get());
        System.out.println(future2.get());
        System.out.println(System.currentTimeMillis() - start);
    }
}

代码执行完最后一行后,发现jvm并没有关闭退出,这是个什么情况呢?

守护线程

先提前说一下守护线程和非守护线程的区别:
守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。

源码解读

在线程池提交任务,也就是submit的时候,如果当时核心线程数量没有超过设置的数量时,会新创建一个核心工作线程(addWorker方法)

也就是new了一个Worker对象,并把提交的任务传递进去

而Worker对象其实就是一个新的线程,可以看到线面讲这个线程设置为了非守护线程(setDaemon(false))。 前面说到在jvm中,当所有的非守护线程都执行完毕之后,虚拟机才会自动退出。

然后再来看一下核心工作线程是怎么执行工作的。 有一个while循环,当一个任务执行完成后,核心工作线程还会不断的获取任务getTask()来执行

下面重点来了,在getTask方法中,也是有一个for循环,看里面的代码,当核心线程允许timeout或线程数量超过核心线程数的时候,timed会被置为true。
timed为true时表示是可以超时关闭的,它会通过任务队列的poll方法获取任务,如果在keepAliveTime时间内获取不到任务在一轮循环中就可能会关闭该线程。
timed为false时不可关闭的,他是通过take方法从任务队列中获取数据,获取不到的时候会阻塞,直到获取到任务才返回。 这样就很清楚了,核心线程在没有任务的情况是一直阻塞的,所以虚拟机也不会退出。

结论

线程池在创建的时候有指定核心线程数量和非核心线程数量,核心线程在创建之后并不会退出,而是一直等待;而非核心线程数量在创建之后会等待相应的时间,若之后没有任务到来则关闭。
所以我们在创建线程池的时候一定要根据需要来指定核心线程的数量,否则会造成资源的浪费。