线程池笔记

发布时间 2023-10-24 17:58:43作者: 之士咖啡
  1. 日常所说的“核心线程”、“非核心线程”是一个虚拟的概念,是为了方便描述而虚拟出来的概念
  2. 在代码中并没有标记哪些线程为“核心线程”或者“非核心线程”。所有线程都是一样的。

线程池是如何实现的?

  在Java中,线程池中所谓的“线程”,其实就是一个静态内部类Worker,它是基于AQS实现的,并实现Runnable。存放在线程池的HashSet<Worker> workers成员变量中。
  而需要执行的任务,则放在BlockingQueue<Runnable> workQueue中。这样,整个线程实现的基础思想就是:从workerQueue中获取任务,放在worker中进行处理。

静态内部类Worker:
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {......}

线程池的核心参数

  • corePoolSize 核心线程数量
  • maximunPoolSize 最大线程数量 ,一般 maximunPoolSize > corePoolSize
  • keepAliveTime 空闲线程的存活时间
  • 存活时间单位
  • workQueue 任务队列(共计5种,按照不同的业务场景使用不同的队列)
  • RejectedExecutionHandler 拒绝策列

线程池的线程会在何时创建与销毁

创建

首先,线程池在创建之初并不会创建线程当任务来临后才会创建第一个线程,每次创建线程都是做出如下判断:

  1. 如果正在运行的线程数量 < corePoolSize,那么马上创建线程运行这个任务;
  2. 如果正在运行的线程数量 >= corePoolSize,那么将这个任务放入队列;
  3. 如果这时候队列满了,而且正在运行的线程数量 < maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
  4. 如果队列满了,而且正在运行的线程数量 >= 于maximumPoolSize,那么线程池会抛出异常RejectExecutionException。

销毁

  当空闲线程大于空闲存活时间,就会销毁掉。直到线程数量等于核心线程数。剩下的线程就是核心线程。

核心线程是否能销毁

  ThreadPoolExecutor中存在一个参数allowCoreThreadTimeOut,此参数表示是否允许核心线程超时,默认为 false,所以一般情况下,是不会销毁的。
  如果调整参数,是可以做到销毁核心线程的。

核心方法- getWork() 获取任务方法

当线程完成当前任务后,会进入死循环,不断的尝试获取任务。

  • 如果 生存时间 > keepAliveTime,会返回 null
  • 如果 生存时间 < keepAliveTime,会阻塞获取
// 线程池源码,获取任务
// 为分析而简化后的代码
private Runnable getTask() {
    boolean timedOut = false;
    for (;;) {
        int c = ctl.get();
        int wc = workerCountOf(c);

        // timed变量用于判断是否需要进行超时控制。
        // allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时;
        // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
        // 对于超过核心线程数量的这些线程,需要进行超时控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if (timed && timedOut) {
            // 如果需要进行超时控制,且上次从缓存队列中获取任务时发生了超时,那么尝试将workerCount减1,即当前活动线程数减1,
            // 如果减1成功,则返回null,这就意味着runWorker()方法中的while循环会被退出,其对应的线程就要销毁了,也就是线程池中少了一个线程了
            if (compareAndDecrementWorkerCount(c))
            return null;
            continue;
        }

        try {
            Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();

            // 注意workQueue中的poll()方法与take()方法的区别
            //poll方式取任务的特点是从缓存队列中取任务,最长等待keepAliveTime的时长,取不到返回null
            //take方式取任务的特点是从缓存队列中取任务,若队列为空,则进入阻塞状态,直到能取出对象为止

            if (r != null) return r;
            timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
        }
    }
}

线程池的拒绝策略

在添加工作任务时,工作队列满后,会执行拒绝策略(都是静态内部类

  • AbortPolicy:直接抛出拒绝异常,默认项
  • CallerRunsPolicy:由线程池的调用者线程去执行多余的方法,串行执行。场景:所有任务都要执行。
  • DiscardOledestPolicy:放弃等待队列中最老的数据,迎接新的数据
  • DiscardPolicy:直接无视,不做任何处理。不添加队列,不报错,不做标识

逻辑:查看 ThreadPoolExcutor.java 文件中 public void execute(Runnable var1) {......} 方法