
线程池实现原理-1
前言
设计到一部分AQS和阻塞队列的内容,可以看一下如下分享
深入理解AbstractQueuedSynchronizer
作用
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
使用
通过Executors类,提供四种线程池
这个看起来好像没有用到线程池,其实是因为没有可复用的线程,所以就一直创建新的线程了
源码
基于jdk1.8.0_20 ,先说一下我的理解,以便对线程池的工作方式有个大概了解,以前我们运行线程的时候new Thread().start()即可,如果线程数多了,频繁的创建线程和销毁线程很费时间,于是Doug Lea将实现了Runnable接口的任务放到一个容器中,然后启动一个线程执行完自己的任务后,还能从容器中拿出任务,调用Runnable接口的run方法,这样一个Thread类就能执行多个任务了,当然可以启动多个线程同时消费容器中的任务,线程池就这样实现了
状态
先了解一下线程池的状态及线程数量的表示方式
AtomicInteger类型的ctl代表了ThreadPoolExecutor的控制状态,是一个复合类型的变量,借助高低位包装了2个概念
- runState 线程池运行状态,占据ctrl的高三位
- workerCount 线程池中当前活动的线程数量,占据ctl的低29位
COUNT_BITS代表了workerCount所占的位数,即29,而CAPACITY表示线程池理论的最大活动线程数量,即536870911
0在Java底层是由32个0表示的,无论左移多少位,还是32个0,即SHUTDOWN的值为0,TIDYING则是高三位为010,低29为0
~是按位取反的意思,CAPACITY表示的是高位的3个0,和低位的29个1,而~CAPACITY则表示高位的3个1,和低位的29个0,然后再与入参c执行按位与操作,即高3位保持原样,低29位全部设置为0,也就获取了线程池的运行状态runState
传入的rs表示线程池运行状态runState,其是高3位有值,低29位全部为0的int,而wc则代表线程池中有效线程的数量workerCount,其为高3位全部为0,而低29位有值的int,将runState和workerCount做按位或,即用runState的高3位,workerCount的低29位填充的数字
构造函数
从上面例子的代码开始看起
这里简单说一下corePoolSize和maximumPoolSize,可以进行如下类比学习,corePoolSize=公司的基本人员,maximumPoolSize=公司的基本人员+外包人员。corePoolSize,保持存活的工作线程的最小数目,当小于corePoolSize时,会直接启动新的一个线程来处理任务,而不管线程池中是否有空闲线程
keepAliveTime是空闲线程的存活时间,默认用于非核心线程,但是当allowCoreThreadTimeOut=true时(这个值默认是false),同样用于核心线程
ThreadFactory是一个工厂类接口,我们可以实现这个接口,来自定义产生线程的方式
来看一下上面的例子使用的默认的工厂类,这个默认的工厂类是Executors类的一个静态内部类
在看看上面例子打印的Thread.currentThread().getName(),你是不是知道因为啥了?
RejectedExecutionHandler是一个接口,有4个实现类,对应4种处理策略,这4个实现类是ThreadPoolExecutor的静态内部类
饱和策略接口,当队列和线程池都满了,说明线程处于饱和状态,必须采取策略处理提交的任务,我们可以实现这个接口来自定义处理策略
来看上面例子中使用的饱和策略的实现方式,其他策略方式实现也挺简单,不再介绍
ThreadPoolExecutor的各种属性,前面基本上介绍的差不多了
execute实现
将线程放入线程池有2种方式,一种是execute,一种是submit,这里我们先说一下execute执行流程
- 首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
- 其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
- 最后线程池判断整个线程池是否已满(即线程数是否小于线程池最大容量)?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。
简单类比一下创建过程
- 来项目了,如果公司有比较闲的基本人员,则让这些闲的基本人员干活
- 公司的基本人员都有活干,则看看以后的日程排满没?没有,则把这些项目排到日程后面,以后干就行
- 项目太多,以后也没时间干,看看公司还有剩余工位不,有工位,来一个项目招一个外包,工位满了,该经理做决定了,是不接这个项目还是其他做法
执行到addWorker(null, false)这个方法说明,任务刚来的时候核心线程都在工作,结果它就被放到阻塞队列中了,然后核心线程都执行完了(并且都被销毁了),如果不调用一下这个方法,则放到阻塞队列中的任务就不会被执行
核心线程不是一直都存在的吗?为什么会被销毁了,这还得从一个属性allowCoreThreadTimeOut说起,这个属性默认是false,意思是核心线程不会被销毁,如果设置为ture,则在workQueue为空的时候,核心线程有可能全被销毁了
reject其实就是根据我们设置的策略,来处理无法运行的任务
文章转载自公众号:Java识堂
