JAVA线程池原理源码解析—为什么启动一个线程池,提交一个任务后,Main方法不会退出?

栏目: Java · 发布时间: 5年前

内容简介:Hello,骚年们,大家新年快乐,头发有没有少呀?今天我们来看一件有趣的事,首先来看段代码呵呵,执行结果谁都知道,显而易见呢?明明这个任务都已经跑完了呀~

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

public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(10);

        service.submit(() -> System.out.println("Hello "));

        System.out.println("World");
    }
复制代码

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

JAVA线程池原理源码解析—为什么启动一个线程池,提交一个任务后,Main方法不会退出?
但是小老弟,有没有发现这个程序 一直都没有结束

呢?明明这个任务都已经跑完了呀~

结论

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

  • 你丑
  • 线程池的创建的时候,第一次 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才会退出?

JAVA线程池原理源码解析—为什么启动一个线程池,提交一个任务后,Main方法不会退出?
网上冲浪 JdkDoc

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Linux Programming Interface

The Linux Programming Interface

Michael Kerrisk / No Starch Press / 2010-11-6 / GBP 79.99

The Linux Programming Interface describes the Linux API (application programming interface)-the system calls, library functions, and other low-level interfaces that are used, directly or indirectly, b......一起来看看 《The Linux Programming Interface》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具