ThreadPoolExecutor线程池内部工作原理及源码解析(上)

本文将深入探讨ThreadPoolExecutor线程池的工作原理和源码实现。通过了解其内部机制,我们可以更好地利用线程池来提高程序的性能和并发能力。

目录

一、线程池解决的问题

二、线程池的核心参数 

三、线程池核心属性 

四、线程池的执行流程

五、线程池的execute方法执行原理

六、线程池的addWorker添加工作线程


一、线程池解决的问题

  1. 线程的频繁创建和销毁会消耗系统资源。
  2. 线程池通过池化技术和线程复用,解决了线程频繁创建和销毁的问题。
  3. 线程池更好地管理了线程的使用情况以及任务的处理数量。
  4. 线程池也可以用来解决任务执行前后需要追加一些额外内容的问题。

然而,线程池也存在一个很大的问题:线程池的参数不好设置。

  1. 如果设置过多的线程,会导致频繁的上下文切换,影响性能,反而降低了效率。
  2. 如果设置过少的线程,则服务器的硬件资源利用不全。

因此,我们需要深入理解线程池的核心源码,掌握线程池的执行流程和细节,然后根据任务的类型来设置更合理的核心参数,以达到优化性能的目的。

二、线程池的核心参数 

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    1、corePoolSize:核心线程数,线程池里的常住户,默认情况下,不会被回收,有活就干,没活闲着
    2、maximumPoolSize:最大线程数,指定了当前线程池中一共有多少工作线程。
                        核心线程数 + 非核心线程数 == 最大线程数
    3、keepAliveTime: 最大空闲时间,非核心线程在空闲了一段时间后,会被干掉,默认只针对非核心线程
    4、unit: 最大空闲时间的单位,纳秒,微秒,毫秒等等单位
    5、workQueue: 工作队列,是存放任务的,当任务处理不过来时,将任务放到工作队列排会队~
                    核心线程都在忙,再有任务投递过来,就直接扔到工作队列
                    核心线程都构建完毕了,再有任务投递过来,就直接扔到工作队列
    6、threadFactory: 线程工厂,由咱们指定Thread对象的构建,可以设置一些thread的信息。
    7、handler: 拒绝策略,当工作线程都在干活,工作队列也扔满了,此时再投递任务到线程池,就拒绝当前任务。
                拒绝的方式很多:默认提供了4中常用的拒绝策略。
}

三、线程池核心属性 

// 要查看线程池的核心属性,就一个,ctl
// 默认是AtomicInteger,就是基于CAS实现的一个原子类,仅此而已,可以把他看成是int类型的一个数值
// ctl维护了两个信息
// 1、标识着当前线程池的状态
// 2、标识这当前线程池中的工作线程个数        工作线程数 == 核心线程 + 非核心线程
// int类型在计算机中,占用了32个bit位。
// 高3位:线程池状态。   低29位:工作线程个数
private int ctl = 0;
// Integer.SIZE ==  32,是int类型占用的bit位个数
private static final int COUNT_BITS = 29;
// CAPACITY:工作线程的最大个数
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
00000000 00000000 00000000 00000001 
<< 29
00100000 00000000 00000000 00000000
-1
00011111 11111111 11111111 11111111  ==  CAPACITY


// 线程池状态的信息
// 111:代表RUNNING状态
private static final int RUNNING    = -1 << COUNT_BITS;
// 000:代表SHUTDOWN状态
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 001:代表STOP状态
private static final int STOP       =  1 << COUNT_BITS;
// 010:代表TIDYING状态
private static final int TIDYING    =  2 << COUNT_BITS;
// 011:代表TERMINATED状态
private static final int TERMINATED =  3 << COUNT_BITS;

 

四、线程池的执行流程

当任务提交给线程池,具体的流程是如何走的。

 

从图中可以看到,ThreadPoolExecutor的工作流程如下:

  1. 当有新任务提交时,如果线程池中的线程数小于CorePoolSize,就会立即创建新线程执行任务。
  2. 如果线程数已经达到CorePoolSize,但任务队列还没有满,则将新任务放入任务队列中等待执行。
  3. 如果任务队列已经满了,且当前线程数小于MaximumPoolSize,则立即创建新线程执行任务。
  4. 如果以上条件都不满足,则根据设定使用默认的拒绝策略处理无法执行的任务。

五、线程池的execute方法执行原理

 execute的核心流程

// 任务提交给线程池后,处理的流程
public void execute(Runnable command) {
    // 健壮性判断,任务不允许为null。
    if (command == null)
        throw new NullPointerException();
    // 获取核心属性ctl(线程池状态,工作线程个数)
    int c = ctl.get();
    // workerCountOf获取工作线程个数
    // 工作线程个数 < 核心线程数
    if (workerCountOf(c) < corePoolSize) {
        // 满足上述要求,创建核心线程(command,true)
        // 创建工作线程成功,返回true,反之,返回false
        if (addWorker(command, true))
            // 工作线程创建成功,任务交给他执行,方法直接结束。
            return;
        // 到这,代表创建工作线程失败,重新获取一次ctl,因为前面成功的addWorker会修改ctl的值
        c = ctl.get();
    }
    // isRunning判断当前线程池状态是否是RUNNING,是RUNNING返回true
    // workQueue.offer(command),将任务扔到工作队列排队。
    // 任务扔到工作队列返回true,如果满了没扔进去,返回false
    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);
}

 拒绝策略有哪些?

拒绝策略,记住4种线程池自带的:
1、AbortPolicy,直接抛异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
2、CallerRunsPolicy,谁提交任务到线程池,谁执行
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}
3、DiscardPolicy,嘛也不管,任务丢了。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
4、DiscardOldestPolicy,将最早提交到工作的队列丢弃掉,重新尝试将当前任务塞进去
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        // 将工作队列中最靠前的取出来
        e.getQueue().poll();
        // 再执行execute
        e.execute(r);
    }
}
5、自定义策略,直接声明class,实现RejectedExecutionHandler接口,重写rejectedExecution,想干啥干啥~

任务扔到工作队列后,做了什么处理?

任务扔到工作队列后,再次判断当前线程池状态是否是RUNNING

  • 如果不是RUNNING,任务不能接收。将任务从工作队列移除,并且执行拒绝策略。
  • 如果是RUNNING,需要再次确认有没有工作线程,如果工作线程是0个,需要构建一个非核心线程去处理工作队列中的任务,避免出现任务饥饿。
if (isRunning(c) && workQueue.offer(command)) {
    // 任务已经扔到工作队列了。
    // 重新拿一次ctl,recheck代表重新检查。
    int recheck = ctl.get();
    // if的判断:是否有并发操作,导致任务刚扔到工作队列,线程池状态就不是RUNNING,
    //          如果是,需要将任务移除并且执行拒绝策略
    // 线程池是RUNNING状态嘛?
    // 如果不是RUNNING,返回false,执行&&后面的操作,将任务从阻塞队列移除。
    // remove如果成功,返回true,失败返回false
    // 如果remove成功,进入if
    if (!isRunning(recheck) && remove(command))
        // 执行拒绝策略。
        reject(command);
    // 线程池状态没问题。是RUNNING。
    // 工作线程个数是不是0个啊?
    // 此时会出现,如果工作线程为0个,工作队列中有任务。
    // 任务饥饿,没工作线程处理
    else if (workerCountOf(recheck) == 0)
        // 创建非核心线程来处理任务
        addWorker(null, false);
}

六、线程池的addWorker添加工作线程

addWorker顾名思义,创建工作线程的

addWorker(参数1,参数2)

  • 参数1:任务,需要线程池处理的任务
  • 参数2:是否是核心线程

在addWorker里有两步操作

操作1:

  • 先判断线程池状态,是否可以创建工作线程
  • 再判断工作线程个数,是否满足构建工作线程的要求

操作2:操作1的判断通过了,才会执行操作2

  • 构建工作线程 ---- 线程工厂
  • 启动工作线程 ---- thread.start()

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值