细品线程池底层源码(一)

细品线程池底层源码一

前言

作为java程序员,练习时候经常会写到线程,而实际在生产项目中更偏向于使用线程池,所以笔者就研究一下线程池的实现。

正文

首先看类结构图
在这里插入图片描述
从最下层的ThreadPoolExecutor类看继续了AbstractExecutorService这个抽象类,而AbstractExecutorService实现了ExecutorService接口。ExecutorService接口继承了Executor接口。
顶层接口Executor就很简单,就定义一个execute执行方法。

 void execute(Runnable command);

从这方法了解到,单单的一个execute是不能满足众多程序猿的需求,于是就出现了ExecutorService接口。
在这里插入图片描述
显而易见的可知ExecutorService继承了Executor并对他进行了很多的扩展,定义了许多方法。在ExecutorService中还增加了对Callable和Future的调用,读者可以简单理解一下,Callable是可拥有返回结果的线程,对应的Future就是用来接收Callable执行的返回结果,和runnable不同runnable创想的线程运行时没有返回结果的。

作为ExecutorService的实现类AbstractExecutorService,具体实现了ExecutorService接口中的方法。
ThreadPoolExecutor作为一个AbstractExecutorService的子类,且不是抽象类,算是线程池的具体实现类。可能很多读者很少使用ThreadPoolExecutor去创建线程池。更偏向于使用Executors工具类去创建。
举个例子

ExecutorService executor = Executors.newSingleThreadExecutor();

简单,方便,实用,无需输入参数就可创建,虽说这样方便了开发者的使用,但也造成了风险,对线程池不正确的创建很容易造成内存泄露的问题。
比如上个例子中提到的Executors.newSingleThreadExecutor()创建单个线程的线程池方法。
观察一下它的方法代码

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

参数定义:

  • 第一个参数是线程池的核心线程数
  • 第二个参数是线程池的最大线程数
  • 第三参数是线程的存活时间
  • 第四个参数是时间单位
  • 第五个参数是等待队列
    其实还有拒绝策略这个参数,不过参数的详细含义会在后文介绍。
    从代码可知,newSingleThreadExecutor方法是new一个ThreadPoolExecutor。
    来看最后一个参数LinkedBlockingQueue,声明做为一个阻塞的无界队列。
    如果出现大量请求访问这个线程池的场景,因为只有一个线程进行处理,就会有大量的请求进入队列去排队,LinkedBlockingQueue参数又声明成了一个无界队列,就会出现一种场景越来越来的请求堆积到队列中,最后导致这个队列吃光了所有内存从而导致内存溢出。
    所以笔者推荐使用线程池的时候显示调用ThreadPoolExecutor的构建方法,这样虽然需要你输入各个参数,但也会加强你对线程池的理解,可以有效避免之前提到的那种场景。

常量定义

老方法,先看有哪些常量

// 核心线程数
 private volatile int corePoolSize;
 // 最大线程数
 private volatile int maximumPoolSize;
 // 空闲线程的存活时间
 private volatile long keepAliveTime;
 // 拒绝策略
 private volatile RejectedExecutionHandler handler;
 // 等待队列
 private final BlockingQueue<Runnable> workQueue;
 // 线程工厂
 private volatile ThreadFactory threadFactory;
  • 核心线程数,即在线程池执行任务的时候会启动线程,即便有其他空余线程,线程池依然会启动一个线程来执行,直到线程内的线程数达到核心线程数的时候,这时候进来的任务就不会启动新的线程去处理,使用其他的空余核心线程去处理。

  • 最大线程数,一个线程池的允许运行的最大线程数量。达到最大线程数是有前提的。先讲下线程池的工作流程

    1. 一个任务进入线程池,判断当前池内是的线程是否小于核心线程数,小于的话创建一个新线程去执行任务。
    2. 如果池内线程已达到核心线程数,则进入workQueue等地队列进行排队。
    3. 如果等待队列是个有界队列且任务满的时候,这时候还有任务进到线程池来,就创建额外的线程去执行任务。
    4. 如果创建额外的线程+核心线程已达到最大线程数了,这时候依然还有任务进来,这时候就会调用拒绝策略,来对这些请求线程池的任务进行处理。

    所以如果等待队列是个无界队列,那maximumPoolSize参数就毫无意义。

  • keepAliveTime 空闲线程的存活时间,默认情况下,只有才线程数超过核心线程数才会生效。

  • workQueue等待队列,存储任务超过核心线程数的任务,也就是所有核心线程都在运行了,还有任务进来就进入等待队列。

  • threadFactory 自定义实现线程的命名方式,通常我们使用默认的线程工厂就足够了,Executors.defaultThreadFactory()

  • handler 拒绝策略,当任务超过最大线程数的时候,对多出的任务进行处理,线程池提供了4种拒绝策略。

  1. AbortPolicy:直接抛出异常,默认策略;
  2. CallerRunsPolicy:用调用者所在的线程来执行任务;
  3. DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  4. DiscardPolicy:直接丢弃任务;

当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

// 第二部分,线程池状态的常量定义
 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
 	// COUNT_BITS 等于整型的最大位数-3 即32-3=29;
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 线程池的最大容量为2的29次方-1 大概500多万
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    // int 类型为32位数据,高三位用来存储线程池的状态,剩下29位用来表达线程池的容量
    // 运行状态
    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;

没错,线程池是有状态的。running 状态字面意思理解就是正常运行的状态,其他四个皆为停止状态,线程池的停止是一个循序渐进的流程,每个流程都拥有不同的状态和做不同的工作。
停止线程的流程

1.Running->SHUTDOWN,正常运行的线程池调用shutdown()方法后,线程池的状态就会变更为SHUTDOWN。当进入SHUTDOWN时,就会停止接收新的任务,而队列中正常正在等待的任务还会正常执行。
2. Running or SHUTDOWN -> STOP, 调用shutdownNow()方法后,线程池会进入stop状态,不仅会停止接收任务,正在运行的任务也会进行中断。
3. SHUTDOWN -> TIDYING:当任务队列和线程池都清空后,会由 SHUTDOWN 转换为 TIDYING
4. TIDYING -> TERMINATED:状态为TIDYING, 会回调terminated() 方法, 当 terminated() 方法结束后,状态变更为TERMINATED。

后话

本文介绍了线程池的工作流程和一些常量定义,以及线程池的类结构关系。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值