ThreadPoolExecutor 线程池解析

256 阅读6分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

为什么要使用线程池

在实际使用中,线程的创建与销毁是很消耗系统资源的,尤其是频繁的创建销毁线程,没有进行有效的管理的话可能会导致系统出现问题。

所以,在需要频繁的创建与销毁线程的时候,推荐使用线程池来处理。

好处

  1. 降低系统资源消耗:通过复用线程来降低线程创建销毁带来的系统性能损耗
  2. 提高线程的可管理性

线程状态

线程状态.png

线程池工作原理

1.png

代码解析

线程池的创建

线程池的创建可以通过 Executors 或者 ThreadPoolExecutor 去创建,在《阿里JAVA编码规范》中是属于强制使用ThreadPoolExecutor 去创建线程池的,由 Executors 创建线程池可能会带来OOM的风险。

构造方法

从构造方法来看ThreadPoolExecutor 线程池的创建方式:

//第一种
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
//第二种
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue,
threadFactory, defaultHandler);
}
//第三种
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit,workQueue,
Executors.defaultThreadFactory(), handler);
}
//第四种 嗯哼,其实上面的三个都是第四种构造方法的重载,所以接下来会围绕第四种进行讲解
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.acc = System.getSecurityManager() == null ? null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

接下来我们来看一下构造参数,一共有7个参数

构造参数

  • corePoolSize 核心线程池大小

当提交一个线程任务时,会首先去检查核心线程池中的线程个数是否达到了 corePoolSize ,如果达到了就不会创建线程去处理任务,如果没有达到,就会创建线程去处理线程任务。

如果调用了 prestartCoreThread() 方法或者 prestartAllCoreThreads() 方法,线程池创建的时候所有核心线程都会被创建并启动。

  • maximumPoolSize 线程池能创建线程的最大数量

当工作队列 workQueue 满了,并且线程池已创建的线程数量小于 maxumumPoolSize 的时候,就会创建线程去处理线程任务。

  • keepAliveTime 空闲线程存活时间。如果当前线程数量超过了 corePoolSize ,并且线程空闲时间超过该阈值的时候,就会将这部分空闲线程销毁,以此降低系统资源占用。
  • unit 时间单位。为 keepAliveTime 指定时间单位
  • workQueue 工作队列。当正在执行线程任务的线程数达到 corePoolSize ,再有新的线程任务进来就会进入工作队列等待被执行。

一般有三种排队策略

  1. 直接交接:使用 SynchronousQueue 作为工作队列,直接将线程任务提交给线程,不进行保留,如果此时没有立即可用的线程来执行任务(达到最大线程数),那么任务将不会进入工作队列。 Tips:使用此队列时会存在线程任务丢失的情况,如果不希望丢失线程任务可以设置 maximumPoolSize 为无限大。

  2. 无界队列:使用 LinkedBlockingQueue 作为工作队列,当所有核心线程都在忙的时候,将导致新任务在队列中等待,在无界队列中, maximumPoolSize 是无效的,线程任务的处理只会由核心线程完成。

  3. 有界队列:使用 ArrayBlockingQueue 作为工作队列,应该与有限的 maximimPoolSize 一起使用。有助于防止资源耗尽,但是调优和控制可能会更加困难。队列大小与最大池大小可能会互相折衷:使用大队列和小池可以最大程度的减少CPU使用率、操作系统资源和上下文开销,但是会人为的降低吞吐量。使用小队列通常需要更大的池大小,这样的话会使CPU繁忙,系统资源消耗比较高。

2.png

  • threadFactory 线程工厂

创建线程的工程类,可以通过指定线程工厂为每个创建出来的线程设置更有意义的名字,以便出现问题的时候方便查找

  • handler 饱和策略

当线程数已满(达到最大线程池数)并且工作队列已满的时候,这个时候线程池就已经达到饱和状态, execute() 将拒绝在method中提交新的任务。此时 execute() 方法将会调用 RejectedExecutionHandler 执行饱和策略来处理新任务。

当前可以采用的饱和策略有四种

  • AbortPolicy :直接拒绝所提交的任务,并且抛出 RejectedExecutionException 异常。默认策略
  • CallerRunsPolicy :只用调用者所在的线程来处理任务,如果调用者所在线程已结束,则丢弃线程任务
  • DiscardPolicy :不处理直接丢弃任务
  • DiscardOlcdestPolicy :丢弃工作队列中存放时间最久的任务,执行当前任务

线程的执行

通过 ThreadPoolExecutor 创建完成线程池,提交任务后的执行过程是怎么样的?

ThreadPoolExecutor 线程池中,可以通过两种方式来提交线程任务

  • submit()
  • execute()

由于 submit() 方法也是通过调用 execute() 方法来提交任务的,所以我们直接来看一下 execute() 的源码

ctl 常量

final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl 这个 AtomicInteger 类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段。它同时同时包含两部分信息:线程池的运行状态(runState)和线程池内的有效线程的数量(workerCount),高3位保存runState,低29位保存workerCount,两个变量互不干扰。

获取内部封装的生命周期状态、获取线程池数量的计算方法如下:

//计算当前线程池运行状态
private static int runStateOf(int c)     { 
  return c & ~CAPACITY; 
} 
  //计算当前线程池中的有效的工作线程数
private static int workerCountOf(int c)  { return c & CAPACITY; }
 //通过状态和线程数生成ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }  

execute()源码

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * 以下分为3个步骤
     * 
     * 1.如果正在运行的线程数少于核心线程数,则创建新线程去执行线程任务;
     *
     * 2. 如果此时线程任务可以成功的加入工作队列中,仍然需要再次检查是否应该添加线程(因为可能存在现有线程在上次检查后就死掉了)或者自进入方法以来该线程池就关闭了,因此需要重新检查状态,并在必要的时候进行回滚队列,如果没有,则启动一个新线程
     *
     * 3. 如果不能添加任务进工作队列,就尝试添加一个新线程来处理该任务,如果失败,则表示线程池已处于关闭或者饱和状态,则执行饱和策略
     */
    //获取ctl的值
    int c = ctl.get();
    //workerCountOf()方法取出低29位的值,表示当前活动的线程数
    //如果活动线程数小于核心线程数,则创建线程执行线程任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        //如果添加失败,重新获取ctl的值
        c = ctl.get();
    }
    //核心线程数已满↓↓↓
    //如果线程池是Running状态,并且添加线程任务到工作队列成功
    if (isRunning(c) && workQueue.offer(command)) {
     //重新获取ctl的值再次检查(检查现有线程是否再上次检查之后死掉或者线程池关闭)
        int recheck = ctl.get();
        //如果此时线程池不是Running状态,并且移除线程任务成功(因为在上面已经将该线程任务添加到工作队列中,所有此时需要将该任务移除)
        if (! isRunning(recheck) && remove(command))
          //执行饱和策略
            reject(command);
        //如果活动线程数为0的话,则执行addWorker方法
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //如果将任务添加到工作队列失败,则创建线程来处理任务
    else if (!addWorker(command, false))
        //执行饱和策略
        reject(command);
}

在以上的源码中,我们可以看到 execute() 方法主要通过调用 addWorker() 方法来创建线程执行线程任务,以及 reject() 方法来执行饱和策略,下面再来看一下这两种方法是如何执行的

addWorker()源码

参数:

  • Runnable firstTask :指定新增的线程执行的第一个任务
  • boolean core :如果为 true 的话,表示新增线程时需要判断当前活动线程数是否少于 corePoolSize ;如果为 false 的话,表示新增线程时需要判断当前活动线程数是否少于 maximumPoolSize
/**
  * 检查是否可以根据当前线程池状态和给定的边界(核心线程数/最大线程数)添加
  * 新的线程任务。如果可以,则将调整工作线程计数,并在可能的情况下创建并启动一
  * 个新的工作线程,并将firstTask作为其第一个任务运行。如果线程池已停止或者关闭,
  * 则此方法返回false,如果在检查时线程工厂无法创建线程,则仍然会返回false。如果
  * 创建线程失败,或者由于线程工厂返回了Null或者出现异常,则会回滚。
  */  
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    //  通过自旋锁来检查是否可以创建线程来处理线程任务
    for (;;) {
       //获取ctl的值
        int c = ctl.get();
        // 获取线程池运行状态
        int rs = runStateOf(c);
        /*
         * 第一个判断条件 
         * 如果rs >= SHUTDOWN ,则表示此时不再接受新任务
         * 后面的组合判断条件,只要有1个不满足,则返回false
         * 1.rs == SHUTDOWN ,关闭状态,不再接受新任务,可以继续处理工作队列种
         * 的任务
         * 2. firstTask为空
         * 3. 工作队列不为空
         * 总结:当线程池状态为SHUTDOWN 的时候就不再接受新的任务了,此时如果
         * 有新的任务进来则直接返回false;然后,如果线程任务为空,工作队列为空,
         * 这个时候也不再需要创建新的线程来处理任务了。
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        for (;;) {
            //获取存活的线程数
            int wc = workerCountOf(c);
            /*
             * 如果存活的线程数大于等于CAPACITY (也就是ctl低29位的最大值),
             * 则返回false
             * 如果存活线程数大于 边界(核心线程数或者最大线程数),也返回false
             */
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 尝试增加工作线程,如果成功,则跳出外层for循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            //增加工作线程失败,再次获取ctl值
            c = ctl.get();  // Re-read ctl
            //判断线程池状态是否等于之前,如果改变的话就返回第一个for循环继续执行
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    //线程池中的每个线程都被封装成一个Worker对象,ThreadPoolExecutor维护的实际上就是一组Worker对象
    Worker w = null;
    try {
        //通过 firstTask 来创建 Worker对象
        w = new Worker(firstTask);
        //每个 Worker 对象都会创建一个线程
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 加锁再次检查
                //获取线程池状态
                int rs = runStateOf(ctl.get());
                /*
                 * rs < SHUTDOWN 
                 * 表示线程池状态为Running,当线程池状态为Running时将直接创建
                 * 线程执行任务;
                 *(rs == SHUTDOWN && firstTask == null) 
                 * 线程状态为SHUTDOWN,并且firstTask 为null,此时就不再接受新
                 * 的任务,但是可以处理工作队列中的任务
                 */
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    //所以此时的线程如果是启动的话就是有问题的了,应该是不启动状      态直接由线程池执行,或者在队列中等待执行
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
             //将Worker对象加入到workers 集合中,该集合存放了池中所有的工作线程
                    workers.add(w);
                    int s = workers.size();
             //largestPoolSize:用来记录线程池中出现的最大线程数量
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                //释放锁
                mainLock.unlock();
            }
            //
            if (workerAdded) {
            //启动线程
                t.start();
                //工作状态为true
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

Worker类

Worker 类继承了 AbstractQueuedSynchronizer 类,实现了 Runnable 接口,用来判断线程是否空闲以及是否可以被中断

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    private static final long serialVersionUID = 6138294804551838833L;
    //在调用构造方法时通过ThreadFactory来创建用来处理任务的线程
    final Thread thread;
    //保存传入的任务
    Runnable firstTask;
    //线程任务计数器
    volatile long completedTasks;
    // 通过给定的任务去创建线程
    Worker(Runnable firstTask) {
    //将AQS的state设置为 -1,默认是0,因为Worker对象刚创建时,还没有执行线程任务,所以此时不应该被中断
        setState(-1); 
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
    /** 通过runWorker 执行任务 */
    public void run() {
        runWorker(this);
    }
    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.
    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;
    }
    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }
    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

runWorer()源码

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    //获取第一个任务
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 调用Worker对象的unlock方法将刚才设置的state 设置为0,也就是允许线程中断
    w.unlock(); // allow interrupts
    // 是否因为异常退出循环
    boolean completedAbruptly = true;
    try {
      //获取线程任务,如果task为空的话,就通过getTask()从工作队列中获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            /*
             * 如果线程池为stop状态的话就要保证线程被中断
             */ 
            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);
    }
}

getTask()源码

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
    //通过自旋锁循环获取线程任务
    for (;;) {
        int c = ctl.get();
        //获取线程池状态
        int rs = runStateOf(c);
        //如果线程池状态 >= SHUTDOWN 并且 队列为空的话就减少工作线程数量
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        //获取工作线程数量
        int wc = workerCountOf(c);
        // timed 变量表示是否需要进行超时控制,allowCoreThreadTimeOut 默认为false,也就是核心线程不允许超时,(wc > corePoolSize):核心线程外的其他线程是需要进行超时控制的
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
        //根据timed判断,如果为true,则使用poll方法进行超时控制,如果在keepAliveTime 存活时间内没有获取到任务则返回null,否则的话就通过take()方法获取任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

processWorkerExit()方法

执行完这个方法后,工作线程就销毁了

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    //因为异常而退出的话,减少工作线程
    if (completedAbruptly)
        decrementWorkerCount();
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

执行流程图

1.png

线程池监控

线程池内置属性

 

  • getTaskCount :线程池已经执行的和未执行的任务总数
  • getCompletedTaskCount :线程池已完成的任务数量,该值小于 taskCount
  • getLargestPoolSize :线程池曾经创建过的最大线程数量,通过该数据可以知道线程是否满过,也就是达到了 maximumPoolSize
  • getPoolSize :线程池当前的线程数量
  • getActiveCount :当前线程池中正在执行任务的线程数量

通过这些方法可以对线程池进行监控,在 ThreadPoolExecutor 类中提供了几个空方法,如 beforeExecute 方法, afterExecute 方法和 terminated 方法,可以扩展这些方法在执行前或执行后增加一些新的操作,例如统计线程池的执行任务的时间等,可以继承自 ThreadPoolExecutor 来进行扩展。

参考文章

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

深入理解Java线程池:ThreadPoolExecutor