title: 线程池源码剖析
date: 2018-5-21 14:18:40
categories:
- JDK
tags:
- JDK
- 代码重构
- 源码学习
摘要:JDK1.8线程池源码剖析
多线程编程中,为每一个任务分配一个线程是不可行的。那么,如何来管理我们的线程,减少系统的性能开销就是我们研究线程池的动力。
JDK1.8
线程池的生命周期
在多线程的环境下为每个任务都分配一个线程是不现实的。线程创建的开销和资源消耗都是很高。所以我们采用线程池的方式来管理我们的线程。
合理的使用线程池能够带来3个好处。
- 降低资源消耗
- 提高响应速度
- 提高了现成的可管理型
线程池的实现原理
当我们向一个线程池提交了一个任务之后,线程池如何处理这个线程呢?
在讨论线程池如何管理线程,我们可以先来看一下,在ThreadPoolExecutor中,线程的生命周期。
线程池使用CAPACITY标志的高3位来表示运行状态
- RUNNING:接收新任务,并且处理任务队列中的任务
- SHUTDOWN:不接收新任务,但是处理任务队列的任务
- STOP:不接收新任务,不出来任务队列,同时中断所有进行中的任务
- TIDYING:所有任务已经被终止,工作线程数量为0,到达该状态会执行terminated()
- TERMINATED:terminated()执行完毕
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;
那个ctl就是标志,是一个原子类
- 当我们向线程池提交一个线程时,如果当前运行的线程少于corePollSize,则创建新线程来执行任务
- 如果运行的线程数 >= corePoolSize 将任务放入BlockingQueue
- 如果BlockingQueue已满的话,穿件新的线程来处理任务
- 如果线程数超过了maximumPoolSize的话,任务将会被拒绝,并调用
rejectedExecution(Runnable r, ThreadPoolExecutor executor);
当然线程的运行要封装为Work才能在运行在线程池中,
具体的源代码如下
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get(); // 获取 线程池数目
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))// 对应我上面的1
return;
c = ctl.get();
}
// 如果线程数多于了corePoolSize,或者线程创建失败,则把command放入到工作队列中去
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);
}
线程池的核心参数:
- corePoolSize:最小存活的工作线程数量(如果设置allowCoreThreadTimeOut,那么该值为 0)
- maximumPoolSize:最大的线程数量,受限于CAPACITY
- keepAliveTime:对应线程的存活时间,时间单位由TimeUnit指定
- workQueue:工作队列,存储待执行的任务
- RejectExecutionHandler:拒绝策略,线程池满后会触发
- ThreadFactory 用于创建线程的工厂,
- keepAliveTime 线程活动保持时间,当线程池的工作线程空闲之后,保持存活的时间。
- TimeUnit:线程活动保持时间的单位,可选的有 DAYS,HOURS,MINUTES…
RejectExecutionHandler策略
- AbortPolicy:默认策略,终止任务,抛出RejectedException
- CallerRunsPolicy:在调用者线程执行当前任务,不抛异常
- DiscardPolicy: 抛弃策略,直接丢弃任务,不抛异常
- DiscardOldersPolicy:抛弃最老的任务,执行当前任务,不抛异常
如何向线程池提交任务线程呢?
可以使用execute()和submit()方法。
void execute(Runnable command);
或者使用ExecutorService的’ Future submit(Callable task);’
Executor ExecutorService接口
原谅我画图的拙劣偷作者一幅图,Executor框架的架构图。
Executor框架主要由三个部分组成。
任务
需要实现Runnable 或者 Callable接口
Executor框架主要有两个关键的实现类ThreadPoolExecutor ScheduledThreadPoolExecutor
实现了ExecutorService接口
jisuanjieguo
实现了 Future接口和实现了Future接口的FutureTask类
Executor框架成员
- ThreadPoolExecutor
ThreadPoolExecutor使用工厂类Executors来创建,Executors可以创建三种类型的ThreadPoolExecutor
- FixedThreadPool
再偷一张
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool适用于为了满足资源需要管理,限制线程数的场景,用于负载较重的服务器
2. SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
单线程的线程池,无需多说
3. CachedThreadPool
CachedThreadPool是一个无界线程池,所谓无界就是没有界限了啦。适用短期任务小程序,负载较轻的服务器,负载较重那搞一个无界服务器会崩掉的啦。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这三个ThreadPoolExecutor的子类线程池主要的区别就在于构造参数workQueue上。
这个WorkQueue使用的是LinkedBlockingQueue
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor主要用来给定的延迟任务进行调度,与Timer类似,但是ScheduledThreadPoolExecutor使用DelayQueue来调度线程,更加灵活,功能更强大,具体就不解释了,想深入研究的可以去看看JDK源码,ScheduledThreadPoolExecutor对线程进行调度的具体实现方法,里面有英文的详细注释。