线程池源码详细解读(下)

前文回顾

  • AQS源码详细解读
  • ReentrantLock源码详细解读
  • LinkedBlockingQueue源码详细解读
  • 线程池源码详细解读(上)

接着上一篇文章,知道线程池的一些相关概念后,一起来看看实现原理吧。
本文讲述ThreadPoolExecutor源码,力求理清执行顺序,尽量保持思路清晰,请耐心看完~

文章导读

  • 内部类-Worker(基本属性,构造方法,AQS相关钩子方法,线程中断方法)
  • 提交任务(execute,addWorker)
  • 执行任务(runWorker)
  • 关闭方法(tryTerminated,shutdown,shutdownNow)

一、内部类-Worker

Worker表示线程池中的每一个任务,与线程一一对应。是AQS的子类,实现其独占模式,封装一些了对于资源操作的方法。

1.1 基本属性

重要的是thread(当前worker线程),firstTask(初始任务),completedTasks(任务计数器)。

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * 这个类永远不会被序列化,设置serialVersionUID
         * 是为了停止javac编译器的警告
         */
        private static final long serialVersionUID = 6138294804551838833L;

        //表示一个工作线程,null则说明线程工厂创建出错了
        final Thread thread;
        //需要运行的初始任务,可能为空
        Runnable firstTask;
        //每个线程的任务计数器,表示完成的任务数量
        volatile long completedTasks;
        ......
     }

1.2 构造方法

Worker在第一次接收任务的时候被线程工厂创建,其中成员变量thread就是基于Worker的线程。

        Worker(Runnable firstTask) {
            //设置AQS.state为-1表示在运行之前禁止被中断
            setState(-1);
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

1.3 对AQS相关方法的实现

Worker既然继承了AbstractQueuedSynchronizer,就一定会有相关钩子方法的实现。钩子方法是isHeldExclusively(),tryAcquire(int unused),tryRelease(int unused)。而lock(),tryLock(),unlock(),isLocked()都是对他们的进一步封装,非常的简练。如果有兴趣可以回忆回忆ReentrantLock都是怎么实现的,比较一下区别。

state表示当前线程的运行状态,总共有3种情况:

  • -1,表示在运行之前禁止被中断。
  • 0,表示锁没有被任何线程获取。
  • 1,表示锁已经被占有。
        //是否持有独占锁,根据state判断
        //state 0:表示锁没有被任何线程获取
        //state 1:表示锁已经被占有
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }
        //尝试获取锁,成功后设置当前线程为锁的持有者
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        //释放锁
        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        //会先调用tryAcquire(1),若拿不到锁则阻塞获取锁资源
        public void lock()        { acquire(1); }
        //尝试获取锁,不会阻塞
        public boolean tryLock()  { return tryAcquire(1); }
        //会先调用tryRelease(1),若释放成功则去等待队列从队尾向前找下一个需要唤醒的节点
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

1.4 对线程的中断方法

interruptIfStarted():用于中断工作线程,保证要中断的thread必须是已经初始化完成的,而且已经运行了。需要注意的是,Worker的构造方法中将state设置为-1。

        void interruptIfStarted() {
            Thread t;
            //确保线程已经运行并且中断标志为还是false时,就执行中断操作。
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

二、提交任务

execute(Runnable command):总共分为3个步骤。

1)如果工作线程数<核心线程数,则每次有新任务来,都创建一个新的线程来处理。调用addWorker()自动检查线程池状态和工作线程数,可以防止一些错误,比如不该创建线程的时候创建线程。

2)当任务成功入队后,我们仍然需要双重检查机制(double-check),检查是否真的需要 添加一个线程。因为某个线程挂了就需要检查,或者进入这个方法后线程池已经关闭了。所以需要再次检查 线程的状态,如果线程池关闭了就有必要回滚进入阻塞队列,或没有线程时启动一个新的线程。

3)如果无法将任务入队,就尝试创建一个线程。如果创建失败了,就表示线程池已经关闭或饱和了 ,就执行拒绝策略。

    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();
            //1.如果线程池不是RUNNING状态,且成功从阻塞队列中删除任务,则该任务由当前 RejectedExecutionHandler 处理。
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 2.否则如果线程池中运行的线程数量为0,则通过addWorker(null, false)尝试新建一个线程,
            //新建线程对应的任务为null。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            //添加任务失败,执行拒绝策略
            reject(command);
    }

addWorker(Runnable firstTask, boolean core):创建工作线程,成功则返回true,并启动线程;失败则返回false,并从工作线程集合中删除。方法比较长,我把主要过程先理一下:

1)首先判断线程池的状态,如果已经处于非运行状态,就看是否满足关闭状态或任务为空或阻塞队列为空。即创建失败。

2)将当前工作线程与核心线程数或最大线程数比较,如果当前线程数比较大,就创建失败。

3)加互斥锁,再次判断异常情况,若出现线程池状态异常的情况,则添加失败,从工作线程集合中移除 。

4)确定无误后创建新的工作线程,添加成功后就启动线程。

再次回顾一下线程池的状态:

  • RUNNING,-1
  • SHUTDOWN,0
  • STOP,1
  • TIDYING,2
  • TERMINATED,3
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 当线程状态为非运行状态,并且满足关闭状态或任务为空或阻塞队列为空时
            //工作线程创建失败,返回false
            if (rs >= SHUTDOWN &&!(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                //当core==true,会将工作线程数与核心线程数比较;
                //当core==false,将工作线程数与最大线程数比较
                //当前线程数大于等于对应的工作线程数或核心线程数,直接返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //如果CAS操作新增工作线程数成功了,就跳出外面的循环
                //失败了则继续循环,自旋尝试
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //刷新ctl
                c = ctl.get();
                //当前线程池状态与之前获取的不一样则说明状态改变了
                //跳回去刷新线程池状态,和之前的一系列关于创建的判断
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //创建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());
                    //当线程池处于运行状态或者处于关闭状态且任务为空
                    //就将当前worker添加进去
                    if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {
                        //再次检查线程是否已启动
                        if (t.isAlive())
                            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的构造方法可以知道,创建的thread传入了this,就是当前的Worker,所以thread.run()实际上调用的就是Worker.run()。
runWorker():Worker.run()会调用该方法,执行相应的的任务。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //获取firstTask
        Runnable task = w.firstTask;
        //清除firstTask
        w.firstTask = null;
        //该方法是addWorker()的一个分支,所以需要释放锁资源。
        w.unlock(); 
        //检测线程是否异常结束的一个标志
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                //如果线程池已经停止了,或中断标志为false则中断当前线程
                //如果线程池处于运行状态,则清除中断标志位
                if ((runStateAtLeast(ctl.get(), STOP)||(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    //执行任务前处理
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //让当前线程执行任务,震惊,竟然直接就调用run方法了
                        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 {
            //处理worker退出
            processWorkerExit(w, completedAbruptly);
        }
    }

getTask():获取任务,出现以下情况会导致返回空,worker退出。
1)当前线程数超过了最大线程数
2)线程池处于stop状态
3)线程池处于shutdown状态并且阻塞队列为空
4)当前worker获取任务等待超时(超过keepalive的时间)。

    private Runnable getTask() {
        boolean timedOut = false; 

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

            // 如果线程池处于非运行状态
            //或者如果处于SHUTDOWN状态且阻塞队列为空
            //则减少工作线程数且返回空
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            //工作线程数是否超过核心线程数
            //是否执行定时获取任务标记
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //满足以下任意一个条件则返回空,worker退出
            //1)工作线程数超过最大线程数
            //2)获取任务超时并且阻塞队列为空或工作线程数超过1个
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                //如果没设置空闲时间,就直接从阻塞队列中获取任务
                //(workQueue.take()是阻塞方法),拿到任务返回,线程被重用
                //如果设置了空闲时间,就在keepAliveTime时间内拿到任务
                //拿不到就返回空
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

processWorkerExit():将worker从工作队列中移除,再看看有没有必要加入新的worker。

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly)
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            //移除worker
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        //尝试终止线程池
        tryTerminate();

        int c = ctl.get();
        //如果线程池的状态是RUNNING或SHUTDOWN,
        //则寻找新的worker,代替死亡的worker
        if (runStateLessThan(c, STOP)) {
            //线程正常终止,completedAbruptly=false是线程异常结束
            if (!completedAbruptly) {
                //找到最小worker的数量
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                //阻塞队列不为空,但最小worker数为0时,需要保留一个
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                //worker数量多,再c黄健
                if (workerCountOf(c) >= min)
                    return;
            }
            addWorker(null, false);
        }
    }

将提交任务,执行任务方法整体概括一下,可以得到线程池对于工作线程数量的控制策略

1)如果工作线程数 < 核心线程数,则创建新的线程处理请求。 
2)如果核心线程数 < 工作线程数 < 最大线程数,则将任务放入阻塞队列,当阻塞队列满的时候才创建新的线程。

四、线程池关闭方法

线程池的关闭方法由

4.1 tryTerminate()--尝试关闭,TIDYING->TERMINATED

tryTerminate():执行线程池的很多操作时都会调用这个tryTerminate(),能够中断空闲的线程。

    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            //线程池处于TIDYING,TERMINATED或
            //处于关闭状态且阻塞队列为空,说明线程池正在终止或不需要终止,不用管。
            if (isRunning(c) || runStateAtLeast(c, TIDYING) ||(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            //当线程数不为0时,中断一个空闲线程
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                //线程池处于TIDYING状态
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        //钩子方法,转换为终止状态
                        terminated();
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));
                        //唤醒所有等待在终止condition上的线程
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
        }
    }

interruptIdleWorkers(boolean onlyOne):中断一个空闲线程,onlyOne为true时,中断一个可能等待任务的线程。如果阻塞队列为空,有的worker就会因为获取task而被阻塞,此时中断那些被阻塞的线程。onlyOne为false时,中断所有空闲线程。

    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //tryLock()能拿到锁,说明此时的worker已经完成任务,成为空闲线程或是被LockSuport.park()阻塞的线程
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

4.2 shutdown()--优雅关闭,RUNNING->SHUTDOWN

shutdown():线程池转换为SHUTDOWN状态,等阻塞队列任务执行完,再转换为TIDYING。

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //检查每一个线程池的线程是否有可以ShutDown的权限
            checkShutdownAccess();
            //更改为SHUTDOWN状态
            advanceRunState(SHUTDOWN);
            //中断所有空闲线程
            interruptIdleWorkers();
            //用于ScheduledThreadPoolExecutor取消延时任务
            onShutdown();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

4.3 shutdownNow()--直接关闭,RUNNING->STOP

shutdownNow():线程池转换为STOP状态,抛弃阻塞队列的任务。

    public List shutdownNow() {
        List tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //直接转换为STOP
            advanceRunState(STOP);
            //中断所有线程
            interruptWorkers();
            //剩余没有执行完的任务将被返回
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

总结

最后,写完已经很不容易了.......大部分的方法都已经涵盖了,线程池设计的还是很严谨的,一次又一次的判断,CAS操作,步步为营。
回答一下上次留的问题,如何设计一个线程池?
首先是线程池的创建,设置核心线程数,最大线程数,空闲时间,阻塞队列,拒绝策略等。其次理清楚执行任务的流程,设置Worker作为每一个线程的代表,完善添加,执行任务方法,接收任务时,得有一系列的安全判断,判断工作线程数,判断池的状态,阻塞队列状况。还要有具体的控制工作线程数的策略,比如小于核心线程数则直接创建worker,执行任务。大于核心线程数小于最大线程数时将任务方法阻塞队列,当阻塞队列满时再创建新的线程。直到创建不了了就执行拒绝策略。每一步操作都要对线程池的状态判定,保证线程安全的,中间可以穿插着tryTerminated,或者时不时的把空闲的线程给中断了。
当发出关闭命令时,可以加互斥锁,改变线程池的状态,此时拒绝新任务,等待任务执行完毕,自旋的方式来中断。

文章若有不当之处,欢迎评论指出~
如果喜欢我的文章,欢迎关注我的知乎专栏Java修仙道路~

你可能感兴趣的:(并发)