Java线程池是如何实现线程复用的?

前言

没看本文,面试挂了,别说没提醒你!
没看本文,面试挂了,别说没提醒你!
没看本文,面试挂了,别说没提醒你!

相信很多人都接触过线程池,我们知道线程池有核心线程和非核心线程之分,其中核心线程是一直存活在线程池中的,而非核心线程是在执行完任务之后超时销毁的。但是大家应该都知道一点,当Thread执行完Runnable任务之后就会销毁,而且就算执行完任务之后把线程挂起也没有办法再去执行其他任务,那线程池是如何做到核心线程复用的呢?下面就通过阅读源码的方法带大家了解背后的原因。

推荐阅读之前的文章
深入浅出Java(Android )线程池ThreadPoolExecutor

大家可以对着这个流程图去学习源码,把这个图掌握了,线程池原理也就差不多了


线程池工作流程

首先来看一下执行线程任务的方法,里面很简单,就是根据工作线程数量去执行不同的策略,里面分成了3种情况,但是都会执行addWoker()方法

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        /*
         * 如果当前活跃线程数小于核心线程数,就会添加一个worker来执行任务;
         * 具体来说,新建一个核心线程放入线程池中,并把任务添加到该线程中。
         */
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

      //程序执行到这里,说明要么活跃线程数大于核心线程数;要么addWorker()失败

        /*
         * 如果当前线程池是运行状态,会把任务添加到队列
         */
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }

       //程序执行到这里,说明要么线程状态不是RUNNING;要么workQueue队列已经满了

        //调用addWorker方法去创建非核心线程,
        //如果当前线程数已经达到 maximumPoolSize,执行拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }

这个方法就和名字一样,添加一个工人来完成任务;而这个工人就是Thread,任务就是Runnable。

private boolean addWorker(Runnable firstTask, boolean core) {
       ...省略一些不重要的

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //Worker是实现了Runnable接口的包装类
            w = new Worker(firstTask);
            //Thread是在Worker构造方法创建的
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());
                  
                    //检查线程池状态,分为2种情况
                    //1、线程池处于RUNNING
                   //2、线程池处于SHUTDOWN并且firstTask==null
                   //这2种情况都会创建Worker来执行队列中的任务
                    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;
    }

到这里,我们大概知道是通过创建Worker来执行任务的,而且线程是在Worker内部创建的,我们也能猜到Thread需要的Runnable应该也在Worker内部,所有我们继续看一下Worker类。

Worker

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
     
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

Worker居然是一个Runnable任务,而且Worker的构造方法中创建了Thread对象。这样的话,在之前的addWorker()方法中调用t.start();就会执行到Worker的run()方法。

继续来看看runWorker()方法,这里很关键,一定要仔细看。

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //这个就是addWorker传进来的Runnable
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //如果task不为null或从workQueue中获取任务不为null
            //就会一直执行
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt

                //检查线程池状态,如果线程池处于中断状态,将调用interrupt将线程中断。 
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    //中断线程
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //线程任务执行啦
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

这里的关键在于这个while()条件判断,当第一次创建Worker时就有任务,当执行完这个任务后,这个方法并没有结束,而是不断地调用getTask()方法从阻塞队列中获取任务然后调用task.run()执行任务。

getTask()获取任务

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // 1.allowCoreThreadTimeOut表示是否允许核心线程超时销毁,默认是false,也就是说核心线程即使空闲也不会被销毁
          //当然,如果设置为true,核心线程是会销毁的
          //这样的话,只有正在工作的线程数大于核心线程数才会为true,否则返回false
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                //2.如果timed为true,通过poll取任务;如果为false,通过take取任务
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

这个方法是通过一个死循环取任务,取任务的话是通过workQueue这个阻塞队列去完成的,在默认不改变allowCoreThreadTimeOut的前提下,如果工作线程数大于核心线程数,则通过poll()从队列取任务;否则通过take()从队列取任务;这2个方法的区别,

  • take():如果队列中任务为空,会调用Condition.await()阻塞当前线程。
  • poll(long timeout, TimeUnit unit):如果队列中任务为空,也会阻塞当前线程,但是阻塞时长为timeout

    这样的话,是不是已经搞清楚线程池中的核心线程复用的原因了。

线程的唤醒是在execute时,当调用workQueue.offer()方法,将任务放入阻塞队列时,会调用Condition.signal()方法唤醒一个之前阻塞的线程。这部分不细讲,感兴趣的同学自行查看。

总结

  • 1、当Thread的run方法执行完一个任务之后,会循环地从阻塞队列中取任务来执行,这样执行完一个任务之后就不会立即销毁了;
  • 2、当工作线程数小于核心线程数,那些空闲的核心线程再去队列取任务的时候,如果队列中的Runnable数量为0,就会阻塞当前线程,这样线程就不会回收了

感谢以下作者

Java线程池实现原理及其在美团业务中的实践
线程池原理
彻底理解Java线程池原理篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335