线程池ThreadPoolExecutor源码分析(一)–ThreadPoolExecutor属性分析
线程池介绍
线程池主要解决两个问题:一 是当执行大量异步任务时线程池能够提供较好的性能在不使用线程池时,每当需要执行异步任务时直接 new 个线程来运行,而线程的创建和销毁是 要开销的 线程池里面的线程是可复用的 ,不需要每次执行异步任务时都重新创建和销毁线程。二是线程也提供了种资源限制和管理的手段,比如可以限制线程的个数,动态新增线程等 每个 ThreadPoolExecutor 也保 些基本的统计数据, 比如当前线池完成的任务数目 等。
另外 线程池也提供了许多可调参数和可 展性接口 ,以满足不同情 需要 ,程员可以使用更方便的 Exe cutors 工厂方法, 比如 newCachedThreadPool (线程池线程可达 nteger.MAX VALUE ,线程自动回收)、 newFixedThreadPool (固定大小的线程池)newSingleThreadExecutor (单个线程)等来 建线程池,当然用户还可自定义。
线程池的主要工作流程
主要的属性
//ctl -> 高3位:表示当前线程池的运行状态 除去高三位之后的低位:表示当前线程中所拥有的线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//Integer.SIZE == 32 COUNT_BITS 是用于存放线程数量的那些位 在当前jdk版本中是29位
private static final int COUNT_BITS = Integer.SIZE - 3;
//表示低COUNT_BITS为 所能表达的最大数值 0001 1111 1111 1111 1111 1111 1111 1111 => 536,870,911
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
线程池ThreadPoolExecutor中的几种状态:
RUNNING : 很正常的运行,接受新任务并且处理阻塞队列里的任务;
SHUTDOWN : 调用了shutdown(),拒绝新任务但是处理阻塞队列里的任务;
STOP : 调用了shutdownnow()马上让他停止,拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务;
TIDYING : 所有任务都执行完(包含阻塞队列里面任务)当前线程池活动线程为 0,将要调用 terminated 方法
TERMINATED : 终止状态,terminated方法调用完成以后的状态
状态变化
//
//1110 0000 0000 0000 0000 0000 0000 0000 转换之后是一个负数
private static final int RUNNING = -1 << COUNT_BITS;
// 0左移29位 还是0 --> 0000 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP = 1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING = 2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED = 3 << COUNT_BITS;
1.RUNNING -> SHUTDOWN:显式调用 shutdown() 方法,或者隐式调用了 finalize(),它里面调用了 shutdown() 方法。
2.RUNNING or SHUTDOWN -> STOP:显式调用 shutdownNow() 方法时候。
3.SHUTDOWN -> TIDYING:当线程池和任务队列都为空的时候。
4.STOP -> TIDYING:当线程池为空的时候。
5.TIDYING -> TERMINATED:当 terminated() hook 方法执行完成时候
获取当前线程池的运行状态 主要就是拿到高3位
// ~0001 1111 1111 1111 1111 1111 1111 1111 -> 1110 0000 0000 0000 0000 0000 0000 0000
// c==ctl == 1110 0000 0000 0000 0000 0000 0000 0111
// &
// 1110 0000 0000 0000 0000 0000 0000 0000
//线程池状态: 1110 0000 0000 0000 0000 0000 0000 0000
private static int runStateOf(int c) { return c & ~CAPACITY; }
获取当前线程池的线程数量 就是拿到低29位
//CAPACITY : 0001 1111 1111 1111 1111 1111 1111 1111
//ctl : 0110 0000 0000 0000 0000 0000 0000 0111
//当前线程池线程数量 : 0000 0000 0000 0000 0000 0000 0000 0111 ==> 7
private static int workerCountOf(int c) { return c & CAPACITY; }
计算ctl的值 (用在重置当前线程池ctl值时 )
//rs 表示线程池状态 wc 表示当前线程池中worker(线程)数量
//111 000000000000000000
//000 000000000000000111
//111 000000000000000111
private static int ctlOf(int rs, int wc) { return rs | wc; }
//任务队列 当线程池中的线程数量达到核心线程数时,再添加任务将会被放倒任务队列
// BlockingQueue 这里的 workQueue 可能是 ArrayBlockingQueue / LinkedBrokingQueue / 同步队列
private final BlockingQueue<Runnable> workQueue;
//线程池全局锁,增加worker 减少 worker 时需要持有mainLock , 修改线程池运行状态时,也需要。
private final ReentrantLock mainLock = new ReentrantLock();
//线程池中真正存放 worker->thread 的地方。 存放线程的地方
private final HashSet<Worker> workers = new HashSet<Worker>();
// termination.await() 会将线程阻塞在这。
// termination.signalAll() 会将阻塞在这的线程依次唤醒
private final Condition termination = mainLock.newCondition();
//记录线程池生命周期内 线程数最大值
private int largestPoolSize;
//记录线程池所完成任务总数 ,当worker退出时会将 worker完成的任务累积到completedTaskCount
private long completedTaskCount;
//是否会维护核心线程数量内的线程空闲时的存活 false: 核心线程空闲不会被清理
private volatile boolean allowCoreThreadTimeOut;
//核心线程数 一般常年干活的线程
//核心池大小是维持生存的最小线程数(并且不允许超时等),除非allowCoreThreadTimeOut = true 在这种情况下最小值为零。
private volatile int corePoolSize;
//线程池中的最大线程数量
private volatile int maximumPoolSize;
//空闲线程存活时间,当allowCoreThreadTimeOut == false 时,会维护核心线程数量内的线程存活,超出部分会被超时。
//allowCoreThreadTimeOut == true 核心数量内的线程 空闲时 也会被回收。
private volatile long keepAliveTime;
//拒绝策略,juc包提供了4中方式,默认采用 Abort..抛出异常的方式。
private volatile RejectedExecutionHandler handler;
//线程工厂 所有线程都使用此创建工厂(通过方法addWorker)
//当我们使用 Executors.newFix... newCache... 创建线程池时,使用的是 DefaultThreadFactory
private volatile ThreadFactory threadFactory;
/*
线程池的几种拒绝策略
* new ThreadPoolExecutor.AbortPolicy() -> 已最大线程数 并且阻塞队列满了 还进来任务会不让它进来并抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() -> 已最大线程数 并且阻塞队列满了 还进来任务会不让它进来并让它去找它原线程执行
* new ThreadPoolExecutor.DiscardPolicy() -> 已最大线程数 并且阻塞队列满了 还进来任务会直接把它丢弃
* new ThreadPoolExecutor.DiscardOldestPolicy() -> 已最大线程数 并且阻塞队列满了 还进来任务会尝试和最早开始的竞争 不会跑出异常
* */
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
ThreadPoolExcutor中的静态内部类Worker任务单元
Worker中包装了一个任务,一个线程、一个记录着当前worker干过多少个任务了
Worker(Runnable firstTask) {
//设置AQS独占模式为初始化中状态,这个时候 不能被抢占锁。
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//使用线程工厂创建了一个线程,并且将当前worker 指定为 Runnable,也就是说当thread启动的时候,会以worker.run()为入口。
//很关键的一步
this.thread = getThreadFactory().newThread(this);
}
线程池的构造方法
System.out.println("查询电脑核心数:" + Runtime.getRuntime().availableProcessors());
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
2, //核心线程数常年工作的线程数量
5, //最大线程数 当核心线程数能应付来的时候(5-2)会休息
2, //(5-2)空闲线程超过2S会自动回收
TimeUnit.SECONDS, //空闲时间单位
new LinkedBlockingDeque<>(5), // 阻塞队列 当用到最大线程以后,允许在该队列中等待5个待处理任务
Executors.defaultThreadFactory(), // 线程池使用默认的就好
new ThreadPoolExecutor.DiscardOldestPolicy() //拒绝策略 也就是当阻塞队列也占满的时候对还要进来处理的任务执行对应的处理方式
);
提交执行task的过程 execute()
public void execute(Runnable command) {
/首先判空,防止空指针的发生
if (command == null)
throw new NullPointerException();
//获取当前线程池ctl状态 后边主要是根据状态来操作
int c = ctl.get();
//workerCountOf(c) 获取当前线程池中的线程数
//条件成立:表示当前线程数量小于核心线程数,此次提交任务,直接创建一个新的worker,对应线程池中多了一个新的线程。
if (workerCountOf(c) < corePoolSize) {
//addWorker 即为创建线程的过程,会创建worker对象,并且将command作为firstTask
//core == true 表示采用核心线程数量限制 false表示采用 maximumPoolSize
if (addWorker(command, true))
return;
//创建失败的情况:
//1.存在并发现象,execute方法是可能有多个线程同时调用的,当workerCountOf(c) < corePoolSize成立后,
// 其它线程可能也成立了,并且向线程池中创建了worker。这个时候线程池中的核心线程数已经达到,所以...
//2.当前线程池状态发生改变了。 RUNNING SHUTDOWN STOP TIDYING TERMINATION
// 当线程池状态是非RUNNING状态时,addWorker(firstTask!=null, true|false) 一定会失败。
//SHUTDOWN 状态下,也有可能创建成功。前提 firstTask == null 而且当前 queue 不为空。特殊情况。
c = ctl.get();
}
//执行到这里有几种情况?
//1.当前线程数量已经达到corePoolSize
//2.addWorker失败..
//isRunning(c)==true 表示当前线程池是运行状态 running && workQueue.offer(command) 添加到任务队列成功
if (isRunning(c) && workQueue.offer(command)) {
//执行到这里说明提交任务成功了 添加到了任务队列
//再次获取线程池的ctl
int recheck = ctl.get();
//! isRunning(recheck) 再次检查!!!!当前线程池不是运行状态 成立:说明你提交到队列之后,线程池状态被外部线程给修改 比如:shutdown()、shutdownNow()
// remove(command) 把刚刚提交的任务删除掉,有两种结果:成功??失败??
//成功:提交之后,线程池中的线程还未消费(处理)
//失败:提交之后,在shutdown() shutdownNow()之前,就被线程池中的线程给处理。
if (! isRunning(recheck) && remove(command))
//提交之后线程池状态为 非running 且 任务出队成功,走个拒绝策略。
reject(command);
//workerCountOf(recheck) == 0 : 当前线程池数量==0 主要是线程池的构造方法允许核心线程数为0
//有几种情况会到这里?
//1.当前线程池是running状态(这个概率最大)
//2.线程池状态是非running状态 但是remove提交的任务失败.
//担心 当前线程池是running状态,但是线程池中的存活线程数量是0,这个时候,如果是0的话,会很尴尬,任务没线程去跑了,
//这里其实是一个担保机制,保证线程池在running状态下,最起码得有一个线程在工作。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//执行到这里,有几种情况?
//1.offer失败
//2.当前线程池是非running状态
//1.offer失败,需要做什么? 说明当前queue 满了!这个时候 如果当前线程数量尚未达到maximumPoolSize的话,会创建新的worker直接执行command
//假设当前线程数量达到maximumPoolSize的话,这里也会失败,也走拒绝策略。
//2.线程池状态为非running状态,这个时候因为 command != null addWorker 一定是返回false。
else if (!addWorker(command, false))
reject(command);
}