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

起因

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线程池,该线程池继承了一个抽象类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的内部类Worker的run方法里

//该类调用了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;
                ...

小结:
这里想说的有两点:

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

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内部类DefaultThreadFactory的newThread方法

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的实现有关

原文地址:http://blog.51cto.com/13981400/2346714

时间: 2024-10-27 02:21:24

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

Spring注解Component原理源码解析

在实际开发中,我们经常使用Spring的@Component.@Service.@Repository以及 @Controller等注解来实现bean托管给Spring容器管理.Spring是怎么样实现的呢?我们一起跟着源码看看整个过程吧! 照旧,先看调用时序图: public AnnotationConfigApplicationContext(String... basePackages) { this(); scan(basePackages); refresh(); } Spring启动

vue双向绑定原理源码解析

当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/maxlove123/simple-Vue.git 1.vue双向绑定原理 vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调.我们先来看Object.definePr

Java集合---Array类源码解析

Java集合---Array类源码解析              ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型:采用改进的归并排序. 1.对于基本类型源码分析如下(以int[]为例): Java对Primitive(int,float等原型数据)数组采用快速排序,对Object对象数组采用归并排序.对这一区别,sun在

java.lang.Void类源码解析_java - JAVA

文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerException if the parent argument is {@code null} * @throws SecurityException if the current thread cannot create a * thread in the specified thread grou

【Java并发编程】21、线程池ThreadPoolExecutor源码解析

一.前言 JUC这部分还有线程池这一块没有分析,需要抓紧时间分析,下面开始ThreadPoolExecutor,其是线程池的基础,分析完了这个类会简化之后的分析,线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法.下面开始分析. 二.ThreadPoolExecutor数据结构 在ThreadPoolExecutor的内部,主要由BlockingQueue和AbstractQu

java动态代理源码解析

众所周知,java动态代理同反射原理一直是许多框架的底层实现,之前一直没有时间来分析动态代理的底层源码,现结合源码分析一下动态代理的底层实现 类和接口 java动态代理的主要类和接口有:java.lang.reflect.Proxy.java.lang.reflect.InvocationHandler 1 java.lang.reflect.Proxy:动态代理机制的主类,提供一组静态方法为一组接口动态的生成对象和代理类. 1.public static InvocationHandler g

JDK 源码解析 —— Executors ExecutorService ThreadPoolExecutor 线程池

零. 简介 Executors 是 Executor.ExecutorService.ThreadFactory.Callable 类的工厂和工具方法. 一. 源码解析 创建一个固定大小的线程池:通过重用共享无界队列里的线程来减少线程创建的开销.当所有的线程都在执行任务,新增的任务将会在队列中等待,直到一个线程空闲.由于在执行前失败导致的线程中断,如果需要继续执行接下去的任务,新的线程会取代它执行.线程池中的线程会一直存在,除非明确地 shutdown 掉. public static Exec

Java集合类库 LinkedList 源码解析

基于JDK 1.7,和ArrayList进行比较分析 Java已经有了ArrayList,用来存放元素,对元素的操作都很方便.为什么还会有LinkedList呢?我们都知道ArrayList获取元素很快,但是插入一个元素很慢,因为ArrayList底层维护的是一个数组,往数组中的某个位置插入一个元素,是很消耗资源的. 而LinkedList插入元素很快,获取任意位置的元素却很慢.这是为什么呢?底层又是怎样实现的呢? 1.继承关系 LinkedList的继承关系图: LinkedList继承的是A

Java 1.7 ThreadPoolExecutor源码解析

相比1.6,1.7有些变化: 1.        增加了一个TIDYING状态,这个状态是介于STOP和TERMINATED之间的,如果执行完terminated钩子函数后状态就变成TERMINATED了: 2.        内部类Worker继承了AQS类作为一个独享锁,在运行每个任务前会获取自己的锁: 3.        runState和poolSize两个字段被合并成一个原子字段ctl了,不再使用mainLock保护了. 原文转载:http://blog.csdn.net/yuenki