Java线程池的使用,含源码分析

线程池

  • 因为创建和销毁线程是比较耗费资源的,所以出现了线程池,能够复用已经创建的对象
    • 能够减低资源消耗
    • 能够提高性能,不需要new线程,线程池里面的线程是可以服用的,不需要每次执行任务都重新创建和销毁
    • 方便线程管理,线程是不饿能随便滥用的,如果不停创建,会导致系统崩溃,而线程池内的线程数量是一定的

Executor框架

  • JKD提供了关于Executor接口的一系列接口、抽象类,我们把这些称为线程池的Executor框架,Executor框架本质上是一个线程池

Executor框架的接口与类结构

  • java.util.concurrent(并发编程的工具)
  • java.util.concurrent.atomic(变量的线程安全的原子性操作)
  • java.util.concurrent.locks(用于锁定和条件等待同步等)
  • Executor
    • ExecutorService
      • AbstractExecutorService
        • ThreadPoolExecutor
        • ForkJoinPool
      • ScheduleExecutorService
        • ScheduleThreadPoolExecutor

ThreadPoolExecutor参数

  • int corePoolSize :指定线程池中的核心数量(最少的线程个数),线程池中会维护一个最小的线程数量,即使他们都处于空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut()方法,默认情况下,创建线程池后,里面是没有线程的,只有当线程任务提交了以后,才会创建线程,如果需要创建线程池后马上创建线程,有以下两种方式
    • prestartCoreThread();初始化一个核心线程
    • prestartAllCoreThread;初始化全部核心线程
  • BlockingQueue workQueue:当核心线程全部繁忙时,又提交了Runnable任务了,就会被放到这个队列里面
  • int maximumPoolSize:指定线程池中允许的最大线程数,当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,知道总线程数达到maximumPoolSize这个上限
  • long keepAliveTime:线程空闲超时时间,如果一个线程处于空闲状态,并且线程数大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁
  • TimeUnit unit:超时的时间单位
  • ThreadFactory threadFactory :线程工厂,用于创建线程,一般默认即可,也可以自己写
    • 默认只有这两个
    • DefaultThreadFactory()和PrivilegedThreadFactory()
  • **RejectedExecutionHandler handler:**拒绝策略,当任务太多来不及处理时,如何拒绝任务
    • JDK内置了4中拒绝策略,都在java.util.concurrent包下面
    • **AbortPolicy:**默认的策略,当任务不能再提交的时候抛出异常
    • **DiscardPolicy:**直接丢弃任务,不跑出异常,就算出了问题,开发者也不知道(用的少)
    • **DiscardOldestPolicy:**丢弃任务中最前面的任务,并执行当前任务(用的少)
    • **CallereRunsPolicy:**交由任务的调用线程来执行当前任务,这种策略能够让所有任务都执行,适合大量计算类型的任务执行,使用这种策略的目标是为了让每个任务都能执行完毕,但是我们使用多线程是为了增大吞吐量而已,(线程池满了以后,我让main线程来执行)
    • 总结:
      • AbortPolicy异常终止策略:异常中止(无特殊场景)
      • DiscardPolicy丢弃策略:无关紧要的任务(文章点击量+1/商品浏览量+1这种任务)
      • DiscardOldestPolicy弃老策略:允许丢掉老数据的场景
      • CallerRunsPolicy调用者运行策略:不允许失败场景(对性能要求不高、并发量较小的功能)
    • 除了这四种策略,还可以通过自定义RejectedExecutionHandler接口,实现自定义拒绝策略
java 复制代码
public class MyRejectedExecutionHandler implements RejectedExecutionHandler{

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor){
        executor.getQueue().offer(r, 60, TimeUnit.SECONDS );
    }
}

线程池拒绝策略应用实践

  • 现在有一个发送短信验证码的需求,由于是营销活动,获取验证码的请求量可能比较大,如果发送短信验证码的操作提交到一个线程池中执行,如果某时刻,获取的验证码请求太多,导致线程池无法处理,就会触发线程池拒绝策略,为了让用户都获得验证码,当线程池处理不过来的时候,希望给重试的机会,所以就自定义了一种线程池拒绝策略
  • 工作流程:
    • 当线程池处理不过来的线程,触发了拒绝策略后,将来不及处理的任务放到Redis中,然后通过定时任务将Redis中的任务读取并运行
  • Spring Boot启动
java 复制代码
@SpringBootApplication
public class Application{
    public static void main(String[] args){
        ApplicationContext ioc = SpringApplication.run(Application.class);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) ioc.getBean("threadPoolExecutor");
        for(int i = 0; i < 15; i++){
            threadPoolExecutor.execute(new MyRunnable(i));
        }
    }
}
  • 线程任务
java 复制代码
public class MyRunnable implements Runnable, Serializable{

    private int i;

    public MyRunnable(int i){
        this.i = i;
    }
    
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName() + ":" + this.i);
    }
}
java 复制代码
public class MyRejectedExecutionHandler implements RejectedExecutionHandler{

    private RedisTemplate<Object, Object> redisTemplate;

    public MyRejectedExecutionHandler(RedisTemplate redisTemplate){
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void rejectedExeciton(Runnable r, ThreadPoolExecutor executor){
        // 1.记录日志(我使用控制台,也可以使用log4j2)
        System.err.println("Task " + r.toString() + " rejected from " + executor.toString());
        // 2.将任务持久化到Redis中
        redisTemplate.opsForList.leftPush("task:key", r);
    }
}
  • 配置类
java 复制代码
@Configuration
public class Config{

    @Bean
    public ThreadPoolExecutor threadPoolExecutor(RedisTemplate<Object, Object> redisTemplate){
        return new ThreadPoolExecutor(
            1,
            1,
            15,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(2),
            Executors.defaultThreadFactory(),
            new MyRejectedExecutionHandler(redisTemplate)
        );
    }

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }

     
}
  • 编写定时任务
java 复制代码
@EnbaleScheduling
@Component
public class MyTask{
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Scheduled(cron = "0/5 * * * * ?")
    public void task(){
        Runnable r = (Runnable) redisTemplate.opsForList().rightPop("task:key");
        if(r != null){
            threadPoolExecutor.execute(r);
            System.our.println("定时任务执行完成");
        }
    }
}

线程池的实现原理

线程池实现源代码

构造方法

  • 线程池的构造方法一共有四个,分别带着5、6、6、7个参数,具体参数详解在上一节,现在对这四个构造函数进行一一分析
  • 五参数线程池构造方法(除了线程工厂、拒绝策略剩下都传了)
java 复制代码
public class ThreadPoolExecutor{

    // 五参数构造方法
    public ThreadPoolExecutor(
        int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue
    ) {
        // 通过this调用类里面的另外一个构造方法
        // 很明显能够看到,这另外一个构造方法传了7个参数
        // 也就是说,调用5参数的构造方法,最后调用的也是7个参数的构造方法
        // 如果没有传入线程工厂或者拒绝策略,都会调用ThreadPoolExecutor中的默认实现
        // 拒绝策略为AbortPolicy,工厂为默认工厂
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
}
  • 六参数构造方法
java 复制代码
public class ThreadPoolExecutor{

    // 六参数构造方法(这个构造方法没有传拒绝策略)
    public ThreadPoolExecutor(
        int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory
    ) {
        // 调用7个参数构造方法
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

    // 六参数构造方法(这个构造方法没有传线程工程)
    public ThreadPoolExecutor(
        int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        RejectedExecutionHandler handler
    ) {
        // 调用7个参数构造方法
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
}
  • 七参数构造方法
java 复制代码
public class ThreadPoolExecutor{

    // 七参数构造方法调用了全部所需要传的
    // 不管是5个参数还是6个参数,最后都会调用这个构造方法
    // 所以这个构造方法是关键方法,接下来就看它做了什么
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        
        // 检查核心线程是否至少能有一个
        // 线程池中线程最大数量是否至少有1个
        // 核心线程数是否还要多过线程池所能拥有的最大数量
        // 非核心线程空闲后销毁时间是否大于0 
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            // 如果有任何一条不符合,直接抛异常
            throw new IllegalArgumentException();

        // 检查阻塞队列、线程工厂、拒绝策略是否为空
        if (workQueue == null || threadFactory == null || handler == null)
            // 如果有任何一个为空,直接抛异常
            throw new NullPointerException();
        
        // 在确认参数没问题后,开始为成员变量赋值
        // 第一个是JDK内置的安全策略,感兴趣可自行了解
        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;
    }
}

execute方法

  • 在我们需要让线程池执行一个任务的时候,往往会需要execute()方法执行线程任务,接下来就具体分析execute方法的源码
java 复制代码
public class ThreadPoolExecutor{
    public void execute(Runnable command) {
        // 检查传入任务是否为空
        if (command == null)
            // 如果为空抛异常
            throw new NullPointerException();
        // 到这里表示任务肯定不为空
        // 通过不知道什么东西得到了一个int值
        // 那么就对这个ctl很好奇了,看看这个东西是什么,跟入源码
        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();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
}
  • 通过跟入execute方法,只看了几行,就发现有我们不知道什么东西的ctl,全程和键盘的ctl键一样,是control,接下来分析ctl
java 复制代码
public class ThreadPoolExecutor{
    // 列出了一些关键成员变量
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    // 上面这句英文是源码自带的,意思是:运行状态被存储在高的比特位
    // 这个高比特位是什么东西,多高叫做高呢?
    // 接下来对它进行分析
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl
    // 上面这句英文是源码自带的,意思是:包装和拆包ctl
    // 什么包装拆包ctl的?看不懂
    // 接下来也对它进行分析
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }
}
  • 其实ctl是一个将线程池的运行状态线程池线程运行数量结合在一个变量的值,通过一个Integer变量能够识别线程池的两个状态,至于为什么将两个状态合二为一的原因,在后面再进行讲解,我们都知道一个Integer其实是有32位bit,这32位bit中需要含有两个状态,先看看线程的运行状态有几种
  • 下面成员变量列出来了几个状态值RUNNINGSHUTDOWNSTOPTIDYINGTERMINATED,一共5个状态(可能有的人好奇,我们学线程的时候不是有六个状态吗,为什么这里只有五个,那些阻塞状态什么的到哪里去了,这里状态是线程池的状态,而不是线程的状态),5个状态很明显需要3个二进制位才能完全表示,那这3个二进制位在Integer的哪个部分?高位?低位?还是中间随便找一个位置放3位?
  • 源码中有一句英文注释,上面给大家翻译了:运行状态被存储在高的比特位,也就是说高3位是运行状态,那么就好解释ctl是什么东西了

ctl是一个原子类的Integer,它一共32位bit,高3位是线程运行状态,剩下的29位是线程运行的数量

  • 下面的部分是解释线程池是怎么样根据ctl识别到线程的运行状态的
  • 先说明线程池运行状态的具体意思**(重要,和后面看源码息息相关,还有记住他们的大小,running最小依次递增)**
    • RUNNING :运行状态,能够接收处理队列中的任务
    • SHUTDOWN :线程池不再接受新任务,但会处理阻塞队列中的任务
    • STOP :线程池不再接受新任务,也不会处理阻塞队列的任务,同时中断正在进行的任务
    • TIDYING:线程池不再接受新任务,所有任务都终止,线程数量为0
    • TERMINATED:线程池已经完全终止,不再有任何活动的线程
java 复制代码
   RUNNING
      |
      V
   SHUTDOWN -----> TIDYING -----> TERMINATED
      |                ^
      V                |
     STOP -------------|
  • 线程池的状态值码
    • RUNNING看源码能够看到,它是根据二进制的移位来实现的

在计算机的世界里面,所有的数字都是补码,且高的第一位是符号位,由0表示正数,1表示负数 -1的原码表示为:100000000 00000000 00000000 00000001 补码的计算根据计算机计算是:从右边往左数找到第一个1,然后1不变,从1左边第一个数字开始由0变1,1变0,但是最高的符号位不变(或者用反码+1都可以,我所说的方式是计算机电路设计底层的方式的转换) -1转换为补码为:11111111 11111111 11111111 11111111 所以RUNNING的状态值是:-1向右比特位移动COUNT_BITS这么多位 COUNT_BITS也是一个成员变量,它的值根据源码也能看到是Integer的大小-3,这个3就是我们高三位的状态值,为什么是减3而不是减其它数字的原因就是这个,也就是32-3=29 RUNNING的二进制值就是-1的补码直接右移29位,得到的数字就是: 11100000 00000000 00000000 00000000 下面的另外四种状态也是相同的道理,有时间可以自己推导: RUNNING = 111 00000 00000000 00000000 00000000 SHUTDOWN = 000 00000 00000000 00000000 00000000 STOP = 001 0000000 00000000 00000000 00000000 TIDYING = 010 00000 00000000 00000000 00000000 TERMINATED = 01100000 00000000 00000000 00000000

  • 为什么将线程池的运行状态线程池线程运行数量两个状态合二为一?
  • 因为这里是线程池,有多个线程一起执行,很容易出现线程安全问题,那么保证线程安全一般会有两种方式:synchronized 锁和cas+volatile它们 使用的场景分别是写多读少写少读多 ,因为线程池的状态和线程数量变化的频率没有那么频繁,所以是使用cas+volatile的方式解决,并且运行状态和线程数量往往需要统一,不能出现一个修改,另一个确没有修改的情况,所以源码采用了原子类的方式,保证这两个值是同时变的。
  • 那么ctl是以什么方式查看到五种状态以及查看线程池的线程数目的呢?
  • 线程池在每次改变线程池的运行状态线程池线程运行数量的时候,都会使用ctlOf(int rs, int wc)这个方法,第一个参数表示运行的状态,第二个参数表示线程运行数量,就拿刚初始化ctl作为例子

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 明显看得到,线程池是被初始化为了:运行状态、运行线程为0,那么ctl是如何进行计算的? rs | wc,是直接进行或操作,或操作就是一个是1就是1,两个都0才为0 rs运行状态经过上面的分析,我们知道就是最高的3位,数量是低29位 111 00000 00000000 00000000 00000000 | 00000000 00000000 00000000 00000000 很明显拼合起来的值就是高三位保留,结果是11100000 00000000 00000000 00000000

  • 当程序运行过程中,能够通过runStateOf(int c)方法获取线程池的运行状态,通过workerCountOf(int c)获得线程池运行数量,这里的c参数在程序中传入的都是经过ctlOf()计算过的值,与上面的ctlOf()方法类似,只是用的是与操作,两个为1才为1,一个为0就是0,因为在CAPACITY这个变量根据上面分析就知道是29个1,高三位都是0,如果我们想要获取数量,就可以直接忽视高三位,因为是与操作,都为1才为1,所以它只负责了低29位,如果想要获取状态,看到源码使用了~CAPACITY表示取反操作,也就是高3位为1,低29位为0,也就是它与传进来的ctlOf()进行与操作,相与起来只会保留高三位,剩下29位肯定都是0

例子: int c = ctlOf(RUNNING, 1); // 设置运行状态位RUNNING,线程数有1个 int rs = runStateOf(c); int rc = workerCountOf(c);

c的计算: 111 00000 00000000 00000000 00000000 | 00000000 00000000 00000000 00000001 结果:11100000 00000000 00000000 00000001 rs计算: 111 00000 00000000 00000000 00000001 & 111 00000 00000000 00000000 00000000 结果:11100000 00000000 00000000 00000000 这个结果与RUNNING状态值一样,所以能判断状态 rc计算: 11100000 00000000 00000000 00000001 $ 00011111 11111111 11111111 11111111 结果:00000000 00000000 00000000 00000001 转换为10进制,表示1,所以能够知道线程数量

这里对成员变量的解析就告一段落,回到execute()方法中去

java 复制代码
public class ThreadPoolExecutor{
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        // 上次分析到这里,现在知道ctl是什么了
        // 这个c表示就是获取到的线程池状态和线程池中线程的数量
        int c = ctl.get();
        
        // 这里的workerCountOf(c),很明显是想要获得线程池中线程运行数量
        // 判断线程池中的线程数量是否 < 核心线程数,如果小于就添加核心线程数量
        if (workerCountOf(c) < corePoolSize) {

            // 进来了表示线程池中运行的线程数量 一定是< 核心线程数
            // 这里使用函数addWorker(线程任务, true)
            // 这里只能知道它是想添加一个核心线程,具体怎么添加的还不知道,稍后继续分析
            if (addWorker(command, true))
                // 添加核心线程成功后直接返回
                return;
            // 走到这里表示没有创建成功,重新获取一下ctl
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
}
  • 分析到这里我们能够知道,我们每次通过execute方法提交线程任务的时候,它都会进行判断当前线程池线程运行数量是否达到我们设置的核心线程数,如果没达到,就创建一个核心线程(但其实线程池内部不区分核心线程和非核心线程,这个true和false只是一个给线程池创建线程的一个条件

比如 ThreadPoolExecutor创建了一个有3个核心线程的线程池 ,使用线程池执行第一个任务时,线程池才会为我创建一个核心线程,而不是3个核心线程,当第一个任务结束的时候,我再次提交一个任务,那么线程池会为我创建第二个核心线程,提交第三个后,才会创建满全部的核心线程

也就是线程池按需加载,也不难想象,创建核心线程的过程必然是需要上锁的,不然可能会导致核心线程高于我们设置的核心线程

java 复制代码
public class ThreadPoolExecutor{
    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();
        }

        // 从这里继续分析
        // 这里判断 线程池是否为运行状态,如果是运行状态,就将任务提交到阻塞队列中去
        // 为什么这么判断?
        // 因为线程池的5中状态中,只有RUNNING状态能够接受新任务
        if (isRunning(c) && workQueue.offer(command)) {

            // 能够进来这个if,就是表示将核心线程满了,并且任务也提交到阻塞队列中去了
            // 并且线程池还在运行状态
            // 这表示任务的处理方式就是等什么时候核心线程有空了,从阻塞队列中取出来执行

            // 再次检查ctl状态值,防止已提交的线程关闭了整个线程池,这里却还在执行(高并发情况下)
            int recheck = ctl.get();

            // 对线程池是否运行状态进行检查
            if ( !isRunning(recheck) && remove(command))
                // 进来这个if表示线程池不是运行状态,并且同时移除阻塞队列的任务成功
                // 表示别的线程关了线程池了(除了running状态以外,剩下状态无法接受任务)
                // 使用我们的拒绝策略,拒绝提交任务
                reject(command);

            // 到这里保证线程池还是在运行的,再判断线程池中线程数量是否为0
            else if (workerCountOf(recheck) == 0)
                // 进来了这个if表示线程池数量0
                // 这个表示可能核心线程也被回收了,并且再上面的判断出现了异常,没有创建核心线程
                // 为了保证线程池的活性,它会创建一个非核心线程处理任务
                // addWorker(任务, 创建线程是否为核心线程)true为核心,false为非核心
                addWorker(null, false);
        }
            
        // 如果到了这个if要么就是线程池没有运行 或者 阻塞队列满了,没有成功提交任务
        else if (!addWorker(command, false))
            // 阻塞队列满了,根据流程可以知道,需要创建非核心线程进行处理,如果没有创建成功
            // 可能是阻塞队列满了,也可能是别的原因
            // 执行拒绝策略
            reject(command);
    }
}
  • 通过这个execute方法的执行流程可以知道,当提交一个任务,核心线程先去处理,处理不了就放到阻塞队列,放入阻塞队列如果没有别的什么异常,直接结束execute方法,等待后续线程池执行
  • 如果放入阻塞队列出现异常,可能是别的线程关闭了线程池,那么采用任务拒绝的方式反馈,并且会移除阻塞队列的任务
  • 如果放入阻塞队列出现异常,也可能是线程池线程数为0(这种情况可能是对线程池设置了核心线程回收命令),从这一段可以知道,哪怕没有创建核心线程的情况下,也有可能先创建的是非核心线程执行任务

总结:execute方法,解释了我们在使用线程池的时候任务的提交过程是什么样的,流程多是因为判断多!

addWorker方法

  • execute方法只是让我们明白了我们通过线程池提交的任务是怎么样的,但是我们还是不清楚线程是怎么创建的(核心线程创建\非核心线程创建)
java 复制代码
public class ThreadPoolExecutor{
    /**
     * @Param firstTask 提交的任务
     * @Param core      创建是否为核心线程 true为核心线程,false为非核心线程
     */
    private boolean addWorker(Runnable firstTask, boolean core) {
        // 标记,也可以看作循环,和汇编方式是一样的,Java也有这种编程方式,只是不推荐使用
        // 用法和汇编是一样的,感兴趣可以了解一下
        retry:
        // 外层死循环
        for (;;) {
            // 获取ctl
            int c = ctl.get();
            // 获取线程池状态
            int rs = runStateOf(c);

            // 对线程池状态再次进行检查
            // 为什么说再次呢,因为创建核心线程的时候,其实没有检查过它的状态
            // 只有创建非核心线程的时候检查过一次

            // shutdown表示只能处理阻塞队列中的任务,但是不能提交新的任务
            // 而只有running状态值比shutdown小
            // 这里判断状态如果是shutdown以上的状态直接返回
            // 如果是shutdown状态,但是没有提交任务,并且阻塞队列不为空
            // 上面两个条件,刚好是shutdown能够做的事情,所以如果不满足上面任何一条,就直接返回创建失败
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))

                // 进来这里面表示,线程池运行状态大于shutdown。这种情况是完全处理不了任务
                // 或者线程池运行状态为shutdown,但是提交了任务
                // 或者线程池运行状态为shutdown,但是阻塞队列为空
                return false;

            // 内层死循环
            // 到这里表示要么running、要么shutdown
            for (;;) {
                // 获取线程运行数量
                int wc = workerCountOf(c);

                // 先判断线程数量是否少于线程池的低29位能容纳的要求(肯定符合的,没有设置线程池这么大)
                // 再判断想创建的是核心线程还是非核心线程
                // 并且判断它们是否符合创建条件(创建后是否满足用户自己设置的核心线程数,以及最大线程数)
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    // 不满足,创建失败
                    return false;
                // 到这里对ctl的值+1,表示创建了一个线程
                if (compareAndIncrementWorkerCount(c))
                    // 如果成功创建了线程的话退出整个循环
                    break retry;

                // 到这里表示没有创建成功线程,重新拿到ctl
                c = ctl.get();
                // 看看线程池状态与刚来的时候是否相同(是否还是running或shutdown)
                if (runStateOf(c) != rs)
                    // 变了的话重新来一次外层循环
                    continue retry;
            }
        }

        // 这下面先不看!!!!!!总结一下
        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;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    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) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
}
  • 这一部分讲了线程的创建,在创建之前先检查了一下线程池的状态,确认是running或者shutdown的话,那就创建一个线程,如果没有创建成功,就检查是不是线程池的状态变了,然后重新来尝试创建,简单理解来说,就是状态检测
java 复制代码
public class ThreadPoolExecutor{
    /**
     * @Param firstTask 提交的任务
     * @Param core      创建是否为核心线程 true为核心线程,false为非核心线程
     */
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        // 只有创建成功了线程才能到下面
        // 这里记录两个状态值,worker状态和worker是否被添加
        boolean workerStarted = false;
        boolean workerAdded = false;
        // 这里创建了一个新Worker,但其实,我们都不知道这个worker是什么东西,稍后分析
        Worker w = null;
        try {
            // 这里初始化了Worker,并且将我们提交的任务放入构造函数
            w = new Worker(firstTask);
            // 从Worker里面拿了一个thread的实例变量
            final Thread t = w.thread;
            // 这里开始判断获取到worker的实例变量
            // 但是我们对worker啥都不知道,这下不得不看了

            // 下面先不看!!!!
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());

                    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是什么东西,里面有什么,所以只能先分析到这里,先分析Worker
java 复制代码
public class ThreadPoolExecutor{

    // Worker是一个内部类,并且实现了Runnable,也就是它也可以是线程
    private final class Worker extends AbstractQueuedSynchronizer implements Runnable{

        private static final long serialVersionUID = 6138294804551838833L;

        final Thread thread;

        Runnable firstTask;

        // 构造方法
        Worker(Runnable firstTask) {
            // 设置这个线程不可被中断
            // 这个不可中断的设置在线程池快要执行之前会放开,先记住
            setState(-1);
            // 将我们提交的线程任务赋值给成员变量
            this.firstTask = firstTask;
            // 通过线程工厂创建一个新的线程出来
            // 这个线程工厂就是我们创建线程池的时候传入的线程工厂(可以自己创建的)
            this.thread = getThreadFactory().newThread(this);
        }

        // 线程run方法
        public void run() {
            runWorker(this);
        }
}
  • 可以看到,通过构造方法,为thread赋值,通过工厂对象进行创建,我是用的DefaultThreadFactory进行分析,如果用了别的,也是一样的道理,这个worker其实就是线程池里面的核心线程和非核心线程
java 复制代码
static class DefaultThreadFactory implements ThreadFactory {

    public Thread newThread(Runnable r) {
        // 创建了一个Thread线程,执行的任务是r,这个r就是我们创建的Worker,它是一个实现Runnable的类
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        // 在这里直接返回
        return t;
    }
}
  • 可能有人被绕晕了,到时候线程是怎么执行的,Worker传进去,而不是我们提交的任务传进去,其实代码就相当于

public class Worker implements Runnable{ public void run(){ mission(); } public static void main(String[] args){ Worker worker = null; worker = new Worker(); Thread t = new Thread(worker); t.start(); } } 执行的还是Worker里面的run方法,并不是直接执行我们提交的任务 所以需要通过Worker里面的run方法执行我们提交的任务

  • 回到代码
java 复制代码
public class ThreadPoolExecutor{
    /**
     * @Param firstTask 提交的任务
     * @Param core      创建是否为核心线程 true为核心线程,false为非核心线程
     */
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 更改ctl的值
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        // 只有创建成功了线程才能到下面
        // 这里记录两个状态值,worker状态和worker是否被添加
        boolean workerStarted = false;
        boolean workerAdded = false;
        // 这里创建了一个新Worker,但其实,我们都不知道这个worker是什么东西,稍后分析
        Worker w = null;
        try {
            // 这里初始化了Worker,并且将我们提交的任务放入构造函数
            w = new Worker(firstTask);
            // 从Worker里面拿了一个thread的实例变量
            final Thread t = w.thread;
            // 判断从线程工厂里面创建的线程是否为空
            if (t != null) {
                // 这里在任务执行前上个锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // 得到线程池状态
                    int rs = runStateOf(ctl.get());
                    // 如果状态时running或者shutdown并且没有提交任务才能执行任务
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 再判断从线程工厂那里获得的线程是否执行
                        // 这里我个人觉得没有必要判断,因为肯定是没有执行的,如果知道原因的话可以在评论区补充
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        // 添加到一个workers队列中去
                        // 这个workers是真正的线程池,里面放满了worker,也就是线程池里面的线程
                        workers.add(w);
                        // 拿到线程池的长度
                        int s = workers.size();
                        // 如果长度大于历史最大,那么为他重新赋值
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        // 表示添加成功
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 如果添加成功
                if (workerAdded) {
                    // 开启线程,执行worker里面的run方法
                    t.start();
                    // 给一个woker正在执行的标记
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
}

总结:线程池的任务其实是由一个Worker对象进行处理的,而线程池的话是Workers的一个Set集合进行存储的,真正的线程池就是Workers并且线程池是没有区分核心线程和非核心线程 ,他只要保留好Worker就好了,到这里线程池已经将所提交的任务放入了Workers,并且已经启动了线程,开启了线程,就表示和原来的main方法已经不同了,main方法接着继续执行它的线程池执行我们提交的任务

线程池是如何执行提交的任务的

  • 到这里已经结束了main方法的执行就是在我们执行完execute后的全步骤,剩下的部分是线程池怎么样执行我们提交的任务,所以接下来的篇幅就是线程池是怎么样执行我们提交的任务的
  • 看到Worker的run方法
java 复制代码
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{

    public void run() {
        runWorker(this);
    }
}
java 复制代码
public class ThreadPoolExecutor{
    final void runWorker(Worker w) {

        // 始终记住Worker就是线程,可以是核心线程,也可以是非核心线程
        // 线程池里面没有对他们进行区分,他只要保留我们设置的值就可以了
        
        // 获取当前线程
        Thread wt = Thread.currentThread();
        // 取出我们放在Worker里面的任务
        Runnable task = w.firstTask;
        // 为Worker中我们提交的的任务置为null(这一步感觉没必要)
        w.firstTask = null;
        // 还记得吗,初始化Worker的时候,我们设置了不可以被中断,到这里解锁了
        w.unlock();
        // 这里提供一个标记,表明执行线程是不是意外退出
        // 如果任务不是正常完成,就不会修改这个值
        boolean completedAbruptly = true;
        try {
            // 如果从上面获取的Worker任务为空的话,就去阻塞队列一直拿任务来执行
            // 只有第一次会执行我们的Worker任务,以后就是进阻塞队列拿任务了

            // 如果有跟着debug的话,当队列里面没有任务,执行到这里卡住是正常情况的,后面会分析是为什么
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // 检测ctl状态查看是否合法
                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 {
                    // 将我们提交的任务置为null
                    // 这样第二次访问的就是阻塞队列中的值
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }

            
            // 执行到这里将标记变为正常完成,没有被中断
            // 如果有跟着debug的话,到上面while循环觉得要跳出后,却没有到这里,是正常情况
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
}

总结:执行Worker的run方法就是执行我们提交的任务,在我们提交的任务的前后,可以自定义切面,在任务执行前后做操作,因为这里相对于前面逻辑没有那么乱,也就不画流程图了

线程池是如何实现线程的回收利用的

  • 其实到这里对线程池的讲解也差不多了,知道了线程池是如何接收我们提交的任务的知道了线程池是如何执行我们提交的任务的 ,但是我觉得还有一个点没有讲到,就是线程池是如何回收非核心线程的
  • 如果跟着debug,会发现在阻塞队列中获取任务的时候会发现任务跟丢了,现在来解释以下为什么会有这种情况,这种情况同时也是线程池实现线程回收的机制,就是getTask方法
java 复制代码
public class ThreadPoolExecutor{
    // 因为从阻塞队列中获取任务执行这一步与线程复用息息相关,所以先从这里开始
    private Runnable getTask() {

        // 这里上来就是一个是否超时的标记,很重要,但是现在看不懂,不知道是做什么的
        boolean timedOut = false;

        // 这里是自旋,表示不断的从阻塞队列中取出任务
        // 两种情况会退出循环
        // 1.获取到任务了
        // 2.非核心线程执行完了并且阻塞队列没东西(上面timeOut标记立大功)
        for (;;) {
            int c = ctl.get();
            // 检测线程池状态
            int rs = runStateOf(c);

            // 如果线程池是stop以上的,进入if
            // 或者线程池是shutdown,但是队列为空,进入if,剩下的情况不进入
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                // 这里会减少一个线程,通过ctl的方式进行减少
                decrementWorkerCount();
                // 因为stop及其以上不能执行任何任务,所以返回null
                // 或者shutdown的时候,但是阻塞队列为空,也是返回null
                return null;
            }

            // 到这里表示是可以执行线程池的状态(running/shutdown且阻塞队列还有东西)
            // 获取线程数量
            int wc = workerCountOf(c);

            // 这个也是一个标记,表示是否需要回收线程(核心线程和非核心线程一起判断的)
            // 其中allowCoreThreadTimeOut这个值表示是否运行核心线程回收
            // 如果在线程池设置的时候设置true表示回收,如果false表示不回收
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            // 如果 线程数 > 线程池最大线程数
            // 如果 超时且需要回收线程
            // 两个只要有一个成立 并且线程池里面只要超过一个线程或阻塞队列为空
            // 核心线程和非核心线程都是在这里进行去除
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                // 进来了里面表示确确实实有线程需要回收
                if (compareAndDecrementWorkerCount(c))
                    // 因为这里也没有任务了
                    // 将线程删除后返回空
                    return null;
                // 如果没有成功,继续下次循环
                continue;
            }

            try {
                // 这里表示线程池的状态检测和线程数量检测都符合,可以获取任务
                // 这个timed表示之前是否设置过需要回收线程
                Runnable r = timed ?
                    // 如果需要会设置一个定时获取,如果没有获取到就是null
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    // 这里会被永久阻塞,也就是为什么核心线程会被留在这里的原因
                    workQueue.take();
                // 如果获取到了任务
                if (r != null)
                    // 直接返回任务
                    return r;
                // 因为如果上面返回了任务,这里就不会执行
                // 如果这里设置了超时,就表示阻塞队列没有任务了,可以直接去除
                timedOut = true;
                // 然后继续自旋
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
}

总结:其实这个getTask主要分为两个部分,关键是对timed的值判断 第一部分:判断线程池状态和判断是否需要减少线程,如果需要减少线程,先减少ctl的值,还没有真正销毁worker,是通过返回的null根据后续逻辑进行销毁 第二部分:获取阻塞队列中的任务进行执行,这里需要注意,去除线程池中的线程是通过timed标记进行判断,如果timed为true表示确实有线程需要被回收,获取阻塞队列的时间为初始化线程池的时间,如果timed为false,表示没有线程需要被回收,获取阻塞队列任务的时间为永久,也就是main线程其实永远不会停下来 timed的值判断:如果核心线程确认回收为true,如果线程池线程数量大于预设值,为true,理解了这个变量,整个流程就很简单了

  • 如果从getTask中返回了null,表示需要去除一个线程,因为不满足while循环条件,所以退出循环,进入finally语句
java 复制代码
public class ThreadPoolExecutor{
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock();
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                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 {
            // 这个就是关键方法,真正删除线程池中线程的操作
            // completedAbruptly这个值如果是正常获取完任务的话都是false
            processWorkerExit(w, completedAbruptly);
        }
    }
}
java 复制代码
public class ThreadPoolExecutor{
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        // 判断程序是否正常终止
        if (completedAbruptly)
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 线程池完成任务数+1
            completedTaskCount += w.completedTasks;
            // 真正的删除线程池中的线程
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        // 下面是保证如果还有任务的话,会将上面移除掉的线程补偿
        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            // 进来这里面表示线程池状态为running或shutdown
            if (!completedAbruptly) {
                // 如果是正常执行完的话,设置最小值
                // 如果设置了核心线程也可以回收的话,最小值就是0
                // 如果没有设置可以回收,最小值就是我们自己设置的核心线程数
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                // 如果设置了核心线程可回收,但是还有工作线程
                if (min == 0 && ! workQueue.isEmpty())
                    // 那就保留一个线程数量
                    min = 1;
                if (workerCountOf(c) >= min)
                    return;
            }
            // 补偿线程
            addWorker(null, false);
        }
    }
}
  • 以上就是线程池的完整分析,希望对大家有用,如果有错误,也欢迎在评论区指正!
相关推荐
.生产的驴1 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲1 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
机器之心2 小时前
AAAI 2025|时间序列演进也是种扩散过程?基于移动自回归的时序扩散预测模型
人工智能·后端
hanglove_lucky3 小时前
本地摄像头视频流在html中打开
前端·后端·html
皓木.4 小时前
(自用)配置文件优先级、SpringBoot原理、Maven私服
java·spring boot·后端
i7i8i9com4 小时前
java 1.8+springboot文件上传+vue3+ts+antdv
java·spring boot·后端
秋意钟4 小时前
Spring框架处理时间类型格式
java·后端·spring
我叫啥都行4 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
Stark、5 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端
coding侠客5 小时前
Spring Boot 多数据源解决方案:dynamic-datasource-spring-boot-starter 的奥秘
java·spring boot·后端