起因
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 线程池, 该线程池继承了一个抽象类 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 的实现有关, 先知道结论, 具体为什么我们留着下期再讲~
总结
跟同事在 codeReview 的时候, 突然聊到单启动线程池, Main 方法会不会死明明已经都结束了呀, 然后就本地跑了试了一下, 跟日常的理解还是不一样的, 查了一下原因, 还是蛮有趣的, 日常工作中多保持好奇心, 不要怕难, 你会越来越强的!
你变强了, 也变秃了(逃
来源: https://juejin.im/post/5c405d026fb9a049fb43f151