概述

这篇文章主要从代码层面分析线程池的工作流程,如果,想直接知道线程池的工作流程,可以看上一篇文章Java线程池

享受源代码的快乐之旅开始了

首先是,总体执行流程

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
        //若工作中的线程数小于核心线程,直接执行
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //核心线程已满,加入堵塞队列
        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);
        }
        //尝试着将任务加入非核心线程,若加入失败,拒绝执行
        else if (!addWorker(command, false))
            reject(command);
    }

总结一下

  1. 核心线程数未满,直接创建新的核心线程来执行任务,若有空闲线程,复用空闲线程
  2. 核心线程数已满,要执行的任务加入堵塞队列
  3. 核心线程数已满,堵塞队列已满,创建非核心线程来执行任务(非时序关联的流程,非核心线程若空闲,并且到达存活时间,会被回收),若有空闲线程,可复用
  4. 核心线程已满,堵塞队列已满,非核心线程已满,任务会被拒绝执行,调用RejectedExecutionHandler

接下来我们看一下是如何创建线程的,只要是addWorker()实现

private boolean addWorker(Runnable firstTask, boolean core) {
    //代码有所裁剪,只是为了可以看得更加简洁
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        //创建核心线程或者非核心线程,由core变量来控制,
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                if (workerAdded) {
                //调起线程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

总结一下
上面所做的事情就是new一个worker对象,并且调用start,如果对Thread和runnable关系比较熟悉的同学,一猜就知道t就是Thread,Wroker就是runnable的二次封装。

接下来我们解密一下Worker

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        Worker(Runnable firstTask) {
        //线程状态,里面维护了线程状态,这篇文章不是将线程状态的,所以这里可以忽略掉,以后有时间可以再仔细讲讲这一块
            setState(-1);
            this.firstTask = firstTask;
            //构造一个线程
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker. */
        public void run() {
        	//由thread.start()方法调用,执行到run()
            runWorker(this);
        }
}

接下来看一下runWorker的方法

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
       
        boolean completedAbruptly = true;
        try {
        //从任务队列中不断取出任务,执行
            while (task != null || (task = getTask()) != null) {
                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);
        }
    }

这个方法所做的事情就是不断从task中取出任务来执行,那么问题来了,按照上面的分析这个线程是结束的呀(一个thread只要run执行结束,那么线程就退出了),那么核心线程是怎么保活的呢?其实所有的谜题都在getTask方法中

private Runnable getTask() {
        boolean timedOut = false; 
            try {
            //判断一下是否是核心线程,非核心线程用poll,核心线程用take,就是这两个方法做到非核心线程多少s超时,核心线程保存。
            //poll方法是若队列为null,那么会等待多少时间,如果在等待时间内队列有新任务加入,那么久立即返回新任务,在这个时间后,那么就返回Null,也就是正常退出了
            //take()方法是若队列为null,那么就一直堵塞,直到队列中有任务加入。所以这就实现了核心线程的保活
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
    }

所以核心线程的保活就是通过堵塞队列来实现的,就是poll和take的奥秘,所以若线程池中没有任务,那么线程是处于堵塞状态,是有消耗cpu资源的,所以核心线程一般情况下别设置太大,一般cpu核数+1即可。当然如果是小型app,设置小点更好

总结

所以线程池的分析就这么多了,希望对大家有所帮助