[Java并发与多线程](十五)线程池源码(下)

1、内部类-Worker

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种情况:
a、-1,表示在运行之前禁止被中断。
b、0,表示锁没有被任何线程获取。
c、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) {
            }
        }
    }
}

2、提交任务

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);
    }

2.1、创建工作线程

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

  1. 首先判断线程池的状态,如果已经处于非运行状态,就看是否满足关闭状态或任务为空或阻塞队列为空。即创建失败。
  2. 将当前工作线程与核心线程数或最大线程数比较,如果当前线程数比较大,就创建失败。
  3. 加互斥锁,再次判断异常情况,若出现线程池状态异常的情况,则添加失败,从工作线程集合中移除 。
  4. 确定无误后创建新的工作线程,添加成功后就启动线程。
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 这里return false的情况有以下几种
            //1.当前状态是stop及以上 2.当前是SHUTDOWN状态,但是firstTask不为空
            //3.当前是SHUTDOWN状态,但是队列中为空
            //从第一节我们知道,SHUTDOWN状态是不执行进来的任务的,但是会继续执行队列中的任务
            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;
    }

这两个方法整体概括一下,可以得到线程池对于工作线程数量的控制策略的一个策略
如果工作线程数 < 核心线程数,则创建新的线程处理请求

3、提交任务

由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、线程池关闭方法

4.1、tryTerminate

tryTerminate():尝试关闭,TIDYING->TERMINATED;执行线程池的很多操作时都会调用这个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

shutdown():优雅关闭,RUNNING->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

shutdownNow():直接关闭,RUNNING->STOP;线程池转换为STOP状态,抛弃阻塞队列的任务。

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

5、如何设计一个线程池?

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

新手也能看懂的线程池学习总结

线程池最佳实践

下一章: 第十六章 ThreadLocal

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值