起因

Hello,骚年们,大家新年快乐,头发有没有少呀?今天我们来看一件有趣的事,首先来看段代码

public static void main(String[] args) {
    ExecutorService service = Executors.newFixedThreadPool(<span class="hljs-number">10</span>);

    service.submit(() -&gt; System.out.println(<span class="hljs-string">"Hello "</span>));

    System.out.println(<span class="hljs-string">"World"</span>);
}
复制代码

呵呵,执行结果谁都知道,显而易见

但是小老弟,有没有发现这个程序 一直都没有结束呢?明明这个任务都已经跑完了呀 ~

结论

开始了吗?不好意思已经结束了,嘻嘻,大过年的不卖关子,我们直接公布答案,造成不退出的原因是这样:

  • 你丑
  • 线程池的创建的时候,第一次submit操作会创建Worker线程(负责去拿任务处理),该线程里写了一个死循环,所以这个Worker线程不会死
  • Worker线程在创建的时候,被设置成了非守护线程thread.setDaemon(false)
  • 早在JDK1.5的时候,就规定了当所有非守护线程退出时,JVM才会退出,Main方法主线程和Worker线程都是非守护线程,所以不会死。

下面我们会就上面几个问题,每一个问题进行源码分析,感兴趣的看官老爷可以继续,看看又不会掉发(逃

源码分析

为什么 Worker 线程不会死

梦开始的地方先从初始化开始

// 该方法利用多台实例化了一个 ThreadPoolExecutor 线程池,该线程池继承了一个抽象类 AbstractExecutorService
ExecutorService service = Executors.newFixedThreadPool(10);
// 调用了 ThreadPoolExecutor.submit 方法也就是父类的 AbstractExecutorService.submit,该方法内部会去调用 execute() 方法
service.submit(() -> System.out.println("Hello"));
复制代码

于是我们定位到ThreadPoolExecutor类的execute方法,我截取了部分如下,注意代码中我打注释的地方

public void execute(Runnable command) {
    ...
        // 如果工作线程还没有超过核心线程数
        if (workerCountOf(c) < corePoolSize) {
            // 去添加工作线程
            if (addWorker(command, true))
                return;
        }
    ...
复制代码

线程池把每一个运行任务的工作线程抽象成了Worker,我们定位到内部addWorker方法

            ...
            // 新建一个 Worker
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                // 下面的操作是将线程添加到工作线程集合里
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 如果添加成功的话
                if (workerAdded) {
                    // 把工作线程跑起来
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
复制代码

这时候一个工作线程也就跑起来了,可以去执行任务了,我们定位到ThreadPoolExecutor的内部类Workerrun方法里

// 该类调用了 runWorker 方法
public void run() {
            runWorker(this);
        }
复制代码
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            // 主要看这个 while, 会看这个 Worker 有没有任务,如果没有就会去取,这里是一个死循环,然后我们定位到 getTask() 方法,看他是怎么取任务的
            while (task != null || (task = getTask()) != null) {
                w.lock();
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
            ...
}
复制代码

这里解释了,工作线程其实不会死(超时时间不在本期范围内),我们继续定位到内部的getTask()方法,看他是怎么取任务的

private Runnable getTask() {
            ...
            // 有没有设置核心线程超时时间(默认没有)当前工作的线程数大于了线程池的核心线城市
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            ...
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    // 调用 workQueue 的 Take 方法,WorkQueue 默认是一个 BlockingQueue,所以调用 take 方法会导致当前工作线程阻塞掉,指到拿到
                    workQueue.take();
                // 如果拿到任务就返回
                if (r != null)
                    return r;
                timedOut = true;
                ...
复制代码

小结:

这里想说的有两点:

  • 工作线程不会死(不设置线程存活时间,默认情况下),会一直拿任务,所以工作线程会一直活着
  • 工作线程拿任务的时候,默认情况下,因为用的是BlockingQueuetake()拿不到任务会阻塞

Worker 线程如何被设置成非守护线程

首先我们来到ThreadPoolExecutor的构造方法里

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
复制代码

构造器里传入了一个ThreadFactory也就是Executors.defaultThreadFactory(),用来产生工作线程,一步一步的点进去我们会定位到Executors内部类DefaultThreadFactorynewThread方法

public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            // 关键代码是这里,把线程设置成了非守护线程
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
复制代码

然后我们看ThreadPoolExector方法去new Worker()的时候

Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            // 这里的 ThreadPool,就是上面提到的那个生产非守护线程的线程工厂
            this.thread = getThreadFactory().newThread(this);
        }
复制代码

看上面的注释下面的内容,为什么是非守护线程就真相大白了。

为什么要等到非守护线程全部结束的时候,JVM 才会退出?

网上冲浪 JdkDoc注意我标蓝的部分,这跟 jvm 的实现有关,先知道结论,具体为什么我们留着下期再讲 ~

总结

跟同事在codeReview的时候,突然聊到单启动线程池,Main 方法会不会死明明已经都结束了呀,然后就本地跑了试了一下,跟日常的理解还是不一样的,查了一下原因,还是蛮有趣的,日常工作中多保持好奇心,不要怕难,你会越来越强的!

你变强了,也变秃了(逃

  • Java

    Java,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台的总称。用 Java 实现的 HotJava 浏览器(支持 Java applet)显示了 Java 的魅力:跨平台、动态的…

    380 引用 • 6 回帖
感谢    赞同    分享    收藏    关注    反对    举报    ...