读书笔记之线程池ThreadPoolExecutor使用以及源码整体分析

前言

平时在项目中经常会用到ThreadPoolExecutor来多线程操作,通过重复利用已创建的线程,降低创建和销毁线程造成的系统资源消耗。本文介绍了ThreadPoolExecutor的使用以及整体的一个框架流程。至于很多细节后续慢慢再去理解。

ThreadPoolExecutor的使用

1.线程池的使用

1.1 线程池的创建

首先看下构造函数:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

内容没什么,无非就是做一些异常判断以及做一些参数赋值。重点是参数,他们的意义以及作用:

  1. corePoolSize(核心线程数上限):当新任务提交上来时,会第一个去判断线程数目是否小于corePoolSize,如果满足条件就会去创建新的线程来执行任务。初始化的时候没有创建任何线程,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法 ,这样才会预先去创建线程。
  2. maximumPoolSize(线程池线程数上限):线程池允许创建的最大线程数,也就是说当前的线程数如果大于maximumPoolSize的话新建的任务就按照拒绝策略来执行了。
  3. keepAliveTime(线程活动保持时间):这个要和allowCoreThreadTimeOut(boolean value)方法结合起来看。如果调用了allowCoreThreadTimeOut(true)的话,当空闲时间达到keepAliveTime则该线程很可能退出。如果调用了allowCoreThreadTimeOut(false)的话,只有线程数目大于核心线程数的时候才会因为空闲时间超时而退出,也就是说核心线程会一直保持着。
  4. unit(keelAliveTime的时间单位):keelAliveTime的时间单位。
  5. workQueue(阻塞队列):阻塞队列。当线程数大于核心线程数时,会把任务塞进阻塞队列。线程池中的线程也会不断的从阻塞队列中取任务执行。
  6. ThreadFactory(线程工厂)用来创建线程,主要是为了给线程起名字
  7. RejectedExecutionHandler(拒绝策略);当线程池线程数已经满了或者处在结束的运行状态,那么就会执行拒绝策略。
1.2 常用线程池的使用

再来看下经常使用三种线程池,他们都是直接利用ThreadPoolExecutor构造函数创建出来的。

单线程池:newCachedThreadPool。该线程池始终保持只有一个线程,多余的任务在阻塞队列中排队。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

固定线程池:newFixedThreadPool(nThreads)。和单线程池类似,只是线程shutdown增加到了nThreads

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

缓存线程池:newCachedThreadPool()。与前两个固定线程池的差别主要是用了SynchronousQueue,他可以看作近似大小为1的缓存队列。同时线程池的最大上限为Integer.MAX_VALUE,可以理解为没上限。那么这个线程池就可以看作为来一个任务就马上处理。而固定线程池是有线程池数目上限的,所以需要LinkedBlockingQueue(可以设置容量大小)来缓存来不及处理的任务。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
1.3 线程池提交任务与关闭任务

提交任务调用方法execute(runnable)并传入一个Runnable即可。而关闭线程池的方法有两种shutdownshutdownNow方法,他们都是遍历所有线程然后调用interrupt方法。但还是有一点差别:

  • shutdown:线程池的状态则立刻变成SHUTDOWN状态。关闭之前已经提交的任务并且拒绝新的任务执行,如果阻塞队列还有任务则执行完才能关闭。
  • shutdownNow:线程池的状态则立刻变成STOP状态。尝试停止所有正在执行的任务并且不处理阻塞队列里的任务,返回在阻塞队列的任务。

2.线程池源码解析

2.1 线程池流程

在这里插入图片描述

这张图是我用来帮助理解线程池,可能有误欢迎指出。

首先项目管理代表着线程池的execute方法,它负责着对任务的安排调度。流水线代表着阻塞队列,元器件代表着任务Runnable对象。员工代表着Worker线程对象。核心工位数代表着核心线程池的数目,临时工位数代表着最大线程数减去核心线程数。

当新任务来的时候,项目管理会顺序执行四个步骤:

  1. 判断核心工位是否满员。不满员的情况下,直接招聘新员工安排任务。新员工在做完项目管理安排的任务后也会去完成流水线上的任务。当流水线上的任务为空时,员工有可能一直在工位上等任务也有可能一段时间没事做后被裁员。
  2. 如果核心工位满员的情况下,把任务放到流水线上。核心工位上的员工会去流水线上取任务。
  3. 如果流水线的元器件都塞满了,那么就判断临时工位是否满员。不满员的话招聘新的员工安排到临时工位上。临时工位上的员工完成手上的也会去流水线上取元器件。如果临时工位的员工一段时间没事做就会被裁员,也就是说流水线上长时间没有元器件的话很可能就会被裁。
  4. 如果临时工位都满了的话,那么就考虑怎么拒绝任务了。

核心工位的员工未满而临时工位上还有员工,那么临时工位的员工就会自动被安排到核心工位直到核心工位满了为止。

2.2 总入口execute方法
/*
 * 在将来的某个时候执行给定的任务,执行该任务的线程可能是一个新建的线程或者从现有的线程池获取中。
 * 如果任务未被执行,有可能是应为线程池关闭了或者线程的数量达到了容量的上限。
 * 分为四个步骤
 */
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    //第一步:如果线程池线程数目小于线程池核心数目,那么创建新的线程处理该任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        //如果走到这一步,有可能线程池异常结束了也有可能达到了corePoolSize的数目。所以重新获取c。
        c = ctl.get();
    }
    //第二步:如果线程池正在运行,将任务添加到workQueue
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        //重新检测线程池状态不是正在运行。那么将任务从队列中移除。成功的话直接拒绝该任务。
        if (! isRunning(recheck) && remove(command))
            reject(command);
        //如果线程池线程数目为0,那么执行一个参数为null的addWorker方法。由于前面有个remove操作,这是避免workQueue无线程处理的异常情况。
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //第三步:添加新的任务
    else if (!addWorker(command, false))
        //第四步:拒绝执行任务
        reject(command);
}
2.3 addWorker方法

用于启动线程执行传进来的任务以及阻塞队列中的任务。

/*
 * Methods for creating, running and cleaning up after workers
 */

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 两种情况返回false,也就是启动线程失败。
        // 1.线程池的运行状态异常,也就是rs >= SHUTDOWN。
        // 2.当线程池的运行状为shutdown的时候,firstTask != null 或者 workQueue.isEmpty()。
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
    
        for (;;) {
            int wc = workerCountOf(c);
            // 三种情况发挥false:
            // 1.线程数目大于CAPACITY,也就是最上限。
            // 2.当前属于核心线程数目的情况下,判断是否超过核心线程的上限。
            // 3.当前属于临时工线程数目的情况下,判断是否超过临时工线程的上限。
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 通过CAS对C自加1操作
            if (compareAndIncrementWorkerCount(c))
                //成功的话跳出最外层for循环
                break retry;
            c = ctl.get(); 
            //如果运行状态不一致,那么调到外层for循环继续检测运行状态。
            if (runStateOf(c) != rs)
                continue retry;
            //如果运行状态一致的话,那么就是线程数目没同步成功,继续内循环。
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        //跑到这一步,说明目前为止线程数目符合条件并且数目增加成功。
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            //上锁, this.mainLock为ThreadPoolExecutor全局的锁。因为以下步骤要开始真正启动线程了。
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                //重新获取运行状态
                int rs = runStateOf(ctl.get());
                //如果状态是运行或者处于关闭状态且firstTask==null(firstTask==null的意图是没有新任务但是要创建一个新线程来执行队列任务)
                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) {
                //如果workers增加成功,那么线程启动
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
2.4 runWorker方法

addWorker方法中如果执行到t.start()并且成功之后,便会调用到Worker对象的的runWorker(this)方法。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    //允许中断。
    w.unlock(); 
    boolean completedAbruptly = true;
    try {
        //如果当前的firstTask为空,那么说明该方法的任务是只希望队列中的任务。也就是从getTask()中不断的取任务执行。
        //如果firstTask不为空,那么首先执行firstTask。
        while (task != null || (task = getTask()) != null)// 即将进行任务相关操作。对任务上锁
            w.lock();
            // 判断分两部分,第一部分:首先判断如果运行状态大于STOP那么进行第二步判断,
            // 否则判断线程是否中断状态并清除标志位,再次判断运行状态大于STOP。如果是true则进行第二步操作。
            // 第二部分:判断当前线程是否处于中断状态
            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);
    }
}
2.5getTask方法

runWorker会不断的去判断(task = getTask()) != null,也就是从阻塞列表中取任务。

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

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
    
        // 两种情况线程数自减1,并返回null
        //第一种 rs大于STOP
        //第二种 rs == shutdown时候并且 workQueue状态为空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
    
        int wc = workerCountOf(c);
    
        // allowCoreThreadTimeOut是允许核心线程退出
        // 如果allowCoreThreadTimeOut = true。那么timed恒为true
        // 接下来的if判断可以看做:
        // if ((wc > maximumPoolSize || timedOut) && (wc > 1 || workQueue.isEmpty())) 也就是说再满足条件的情况下线程可以退出。
        // 如果allowCoreThreadTimeOut = false。那么boolean timed = wc > corePoolSize;如果wc大于核心线程数,那么和上面一样符合条件可能退出。如果wc小于核心线程数,那么判断条件视为:
        // 那么接下来的if判断 wc > maximumPoolSize明显不成立同时timed && timedOut也不成立。那么整个if判断都不成立,getTask()的for循环就会一直运行下去。
        
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            //如果CAS自减1失败,再进行该操作
            continue;
        }
    
        try {
            //从workQueue中取出任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            //取任务失败
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

结语

后续会学习的知识点:

1、有关AQS知识以及Worker内部类

2、CAS实现原子操作

3、线程池的阻塞队列原理

4、拒绝策略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值