博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA线程池原理源码解析—为什么启动一个线程池,提交一个任务后,Main方法不会退出?...
阅读量:6258 次
发布时间:2019-06-22

本文共 5613 字,大约阅读时间需要 18 分钟。

起因

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

public static void main(String[] args) {        ExecutorService service = Executors.newFixedThreadPool(10);        service.submit(() -> System.out.println("Hello "));        System.out.println("World");    }复制代码

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

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

结论

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

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

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

源码分析

为什么Worker线程不会死

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

//该方法利用多台实例化了一个ThreadPoolExecutor线程池,该线程池继承了一个抽象类AbstractExecutorServiceExecutorService 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
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方法会不会死明明已经都结束了呀,然后就本地跑了试了一下,跟日常的理解还是不一样的,查了一下原因,还是蛮有趣的,日常工作中多保持好奇心,不要怕难,你会越来越强的!

你变强了,也变秃了(逃

转载地址:http://yxasa.baihongyu.com/

你可能感兴趣的文章
NBIoT三种部署方式【转】
查看>>
Linux 内核驱动--多点触摸接口【转】
查看>>
vim快捷键笔记【原创】
查看>>
算法(Algorithms)第4版 练习 2.3.17
查看>>
详解JSOUP的Select选择器语法
查看>>
条款12:复制对象的时候不要忘了其每一个部分
查看>>
一统江湖的大前端(3) DOClever——你的postman有点low
查看>>
解决浏览器Adobe Flash Player不是最新版本问题
查看>>
KMP
查看>>
5.基于优化的攻击——CW
查看>>
cocos2d-x的CallFunc
查看>>
customTextbox
查看>>
oracle11g安装完成后修改字符集
查看>>
Laravel 的HTTP控制器
查看>>
结构型 之 适配器模式
查看>>
CAD导板框方法
查看>>
[CF1039D]You Are Given a Tree
查看>>
[LeetCode] Climbing Stairs 斐波那契数列
查看>>
【jUploader】1.0版 基于jQuery文件无刷新上传插件下载及介绍
查看>>
Html5 web 储存
查看>>