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的实现有关
- 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之submit方法 (二)
- 【dubbo源码解读系列】之二 dubbo代码启动入口解析(自定义main方法)
- Java线程池架构原理和源码解析(ThreadPoolExecutor)
- 在java中为什么要把main方法定义为一个static方法?
- 并发编程(十三)—— Java 线程池 实现原理与源码深度解析 之 Executors(三)
- java中为什么要把main方法定义为一个static方法?
- Java线程池架构原理和源码解析(ThreadPoolExecutor)
- Java中的static关键字解析(转自海子)__为什么main方法必须是static的,因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。
- 【Java实战】源码解析为什么覆盖equals方法时总要覆盖hashCode方法
- Java线程池架构原理和源码解析(ThreadPoolExecutor)
- java线程池架构原理和源码解析(ThreadPoolExecutor)
- 在java中为什么要把main方法定义为一个static方法?
- Java线程池架构原理和源码解析(ThreadPoolExecutor)
- 在java中为什么要把main方法定义为一个static方法
- Java线程池架构原理和源码解析(ThreadPoolExecutor)
- 解析xml的几种方法,他们的原理,比较 以及JAVA源码
- Java线程池架构原理和源码解析(ThreadPoolExecutor)
- Java线程池架构原理和源码解析(ThreadPoolExecutor)
- [Java多线程]-线程池的基本使用和部分源码解析(创建,执行原理)
- 解析xml的几种方法,他们的原理,比较 以及JAVA源码