一、基本架构
在我们使用线程就去创建一个线程,如果并发的线程过多,频繁的创建线程就会大大降低系统的效率(创建线程需要时间)
有了线程池,每有一个任务,从线程池调度一个空闲的线程来执行任务,就避免了每次都去创建线程带来的开销
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
1、Executor
一个运行新任务的简单接口
public interface Executor {
//在将来的某个时间执行给定的命令。 该命令可以在一个新线程,一个合并的线程中或在调用线程中执行,由Executor实现。
void execute(Runnable command);
}
2、ExecutorService
扩展了Executor接口。添加了一些用来管理执行器生命周期和任务生命周期的方法
3. AbstractExecutorService
该类实现了ExecutorService接口,为线程池提供了默认实现,是线程池的基础骨架。
4. ThreadPoolExecutor
该类就是JAVA的线程池,继承了AbstractExecutorService
5. ScheduledExecutorService
该接口提供了延时和周期性的任务执行功能
6. ScheduledThreadPoolExecutor
带延时和周期性任务执行的线程池
7. Executors
线程池的静态工厂,通过该静态工厂返回常用的线程池
二、ThreadPoolExecutor
ThreadPoolExecutor就是我们经常说的大名鼎鼎的线程池,Executors工厂创建的线程池都是该类的实例,通过调节参数的大小创建适用于各个场景的线程池。ThreadPoolExecutor继承了AbstractExecutorService,该抽象类为线程池提供了默认实现
其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。
构造函数
public ThreadPoolExecutor(int corePoolSize,//线程池初始启动时线程的数量
int maximumPoolSize,//最大线程数量
long keepAliveTime,//空闲线程多久关闭?
TimeUnit unit,// 计时单位
BlockingQueue<Runnable> workQueue,//放任务的阻塞队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler// 拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
这个构造函数有很多参数,我们分别来解释每一个参数的意义
- corePoolSize:核心线程数,当提交一个新的任务到线程池,如果当前线程池运行的线程数(包括闲置的线程)小于核心线程数,则会创建一个新的线程作为核心线程来执行该任务。
- maximumPoolSize:线程池允许最大的线程数,当提交一个新的任务到线程池,如果当前线程池运行的线程数(包括闲置的线程)大于corePoolSize,小于maximumPoolSize,并且等待队列满的时候,会创建一个新的线程来处理该任务。
- keepAliveTime:当线程池中线程数量大于corePoolSize时,闲置线程最长可以存活的时间。
- unit:时间单位。
- workQueue:保存任务的队列,当池中线程数大于corePoolSize时,新来的任务保存到该队列。
- threadFactory:线程工厂,线程池中的线程都是通过这个工厂创建的。
- handler:任务拒绝执行策略,当线程池无法处理新来任务时的处理策略。
-
handler的拒绝策略:
有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
第二种DisCardPolicy:不执行新任务,也不抛出异常
第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
第四种CallerRunsPolicy:直接调用execute来执行当前任务
讲到这,有必要讲下ThreadPoolExecutor的设计思路,了解了设计思路对后面源码的分析会有更好的效果。
通过上图我们介绍下线程池的设计思路:
- 当一个任务通过submit或者execute方法提交到线程池的时候,如果当前池中线程数(包括闲置线程)小于coolPoolSize,则创建一个线程执行该任务。
- 如果当前池中线程数大于等于coolPoolSize,则将该任务加入到等待队列。
- 如果任务不能入队,说明等待队列已满,若当前池中线程数小于maximumPoolSize,则创建一个临时线程(非核心线程)执行该任务。
- 如果当前池中线程数已经等于maximumPoolSize,此时无法执行该任务,根据拒绝执行策略处理,后面还会详细讲解具体的拒绝执行策略。
以上4步是线程池处理到达任务的主要流程。当池中线程数大于coolPoolSize,超过keepAlive时间的闲置线程会被回收掉。注意,回收的是非核心线程,核心线程一般是不会回收的。如果设置allowCoreThreadTimeOut(true),则核心线程在闲置keepAlive时间后也会被回收。
任务队列是一个阻塞队列,线程执行完任务后会去队列取任务来执行,如果队列为空,线程就会阻塞,直到取到任务。
接下来继续学习源码:
submit方法
通常情况下我们通过submit方法向线程池提交并执行任务,线程池的submit方法都是在子类AbstractExecutorService实现的,并且有多个重载的方法,看下这些方法的实现:
//提交Runnable的任务,通过newTaskFor方法包装成RunnableFuture
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
//指定返回值为null
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
//提交Runnable的任务,指定返回值为result
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
//提交Callable的任务
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
这几个重载方法首先通过newTaskFor方法将任务包装成一个RunnableFuture,然后调用execute方法执行任务。
先看下newTaskFor方法:
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
这两个重载方法很简单,创建一个FutureTask对象返回,之后我们就可以通过这个对象的get方法获取任务执行结果了。
AbstractExecutorService这几个重载的submit方法最终都调用了execute方法,通过该方法真正执行任务。
execute方法
execute是真正执行任务的方法,分析execute源码之前先来看下ThreadPoolExecutor的状态字段定义:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//COUNT_BITS计算后等于29,活动线程数占用的位数
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
//线程池5种运行状态,保存在ctl高3位
//11111111 11111111 11111111 11111111左移29位后只保留高位3个1即:
//11100000 00000000 00000000 00000000
private static final int RUNNING = -1 << COUNT_BITS;
//0左移29位后
//00000000 00000000 00000000 00000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//1左移29位后
//00100000 00000000 00000000 00000000
private static final int STOP = 1 << COUNT_BITS;
//2左移29位后
//01000000 00000000 00000000 00000000
private static final int TIDYING = 2 << COUNT_BITS;
//3左移29位后
//01100000 00000000 00000000 00000000
private static final int TERMINATED = 3 << COUNT_BITS;
线程池维护了一个int原子变量ctl,表示线程池当前状态。通过这一个字段表示线程池当前活动线程数和线程池的运行状态。其中低29位用来表示活动线程数,高3位用来表示线程池的运行状态。
线程池的运行状态有RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。状态所对应的数字其实没有什么意义,重点需要了解状态代表的含义。
- RUNNING:该状态下的线程池可以接受新任务,并且可以处理等待队列中的任务。
- SHUTDOWN:该状态下的线程池不再接受新任务,但是可以处理等待队列中的任务。
- STOP:该状态下的线程池不再接受新任务,不再处理等待队列中的任务,会中断正在执行的任务。
- TIDYING:所有的任务都已经中止,活动线程数为0,此状态下的线程池即将转移到TERMINATED状态。
- TERMINATED:terminated()执行完后到达此状态。
线程池的状态转移包括如下几个:
- RUNNING -> SHUTDOWN,在执行shutdown()方法时,线程池经历了这种状态转移过程。
- RUNNING -> STOP或者SHUTDOWN -> STOP,在执行shutdownNow()方法时,线程池经历了这种状态转移过程。
- SHUTDOWN -> TIDYING,当等待队列和池中的任务都为空时,经历了这种状态转移过程。
- STOP -> TIDYING,池中任务为空时,经历这种状态转移过程。
- TIDYING -> TERMINATED,执行terminated()方法时经历这个状态转移过程。
接下来看execute的源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// clt记录着runState和workerCount
int c = ctl.get();
//workerCountOf方法取出低29位的值,表示当前活动的线程数
//然后拿线程数和 核心线程数做比较
if (workerCountOf(c) < corePoolSize) {
// 如果活动线程数<核心线程数
// 添加到
//addWorker中的第二个参数表示限制添加线程的数量是根据corePoolSize来判断还是maximumPoolSize来判断
if (addWorker(command, true))
// 如果成功则返回
return;
// 如果失败则重新获取 runState和 workerCount
c = ctl.get();
}
// 如果当前线程池是运行状态并且任务添加到队列成功
if (isRunning(c) && workQueue.offer(command)) {
// 重新获取 runState和 workerCount
int recheck = ctl.get();
//重新判断线程池是否处于RUNNING状态,若不处于RUNNING状态,删除等待队列中该任务并拒绝任务
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
//第一个参数为null,表示在线程池中创建一个线程,但不去启动
// 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize
addWorker(null, false);
}
//任务添加到等待队列失败,尝试创建一个非核心线程执行该任务,创建失败则拒绝执行任务
//但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize
else if (!addWorker(command, false))
//如果失败则拒绝该任务
reject(command);
}
//当前活动线程数量
private static int workerCountOf(int c) {
//c & 00011111 11111111 11111111 11111111
//"与"运算取低29位的值
return c & CAPACITY;
}
execute的执行逻辑其实前面已经提到了,这里根据代码再分析下:
- 对于空的任务,线程池会抛出NPE(空指针)异常
- 通过workerCountOf方法获取线程池的线程数,若线程数小于核心线程数,创建一个核心线程并将任务作为该核心线程的第一个任务。若创建线程失败,重新获取线程池状态。
- 尝试将任务添加到等待队列,需要注意的是,任务添加到等待队列成功后,需要进一步检查线程池状态,因为这个过程线程池的状态可能已经改变。
- 尝试将任务添加到等待队列,添加失败拒绝执行任务。
workCountOf方法很简单,通过”与”运算取ctl的低29位的值。
看下addWorker方法,该方法是线程池的重点:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
// 获取运行状态
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 如果状态值 >= SHUTDOWN (不接新任务&不处理队列任务)
// 并且 如果 !(rs为SHUTDOWN 且 firsTask为空 且 阻塞队列不为空)
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
// 返回false
return false;
for (;;) {
//获取线程数wc
int wc = workerCountOf(c);
// 如果wc大与容量 || core如果为true表示根据corePoolSize来比较,否则为maximumPoolSize
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 增加workerCount(原子操作)
if (compareAndIncrementWorkerCount(c))
// 如果增加成功,则跳出
break retry;
// wc增加失败,则再次获取runState
c = ctl.get(); // Re-read ctl
// 如果当前的运行状态不等于rs,说明状态已被改变,返回重新执行
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 根据firstTask来创建Worker对象
w = new Worker(firstTask);
// 根据worker创建一个线程
final Thread t = w.thread;
if (t != null) {
// new一个锁
final ReentrantLock mainLock = this.mainLock;
// 加锁
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
// 获取runState
int rs = runStateOf(ctl.get());
// 如果rs小于SHUTDOWN(处于运行)或者(rs=SHUTDOWN && firstTask == null)
// firstTask == null证明只新建线程而不执行任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 如果t活着就抛异常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 否则加入worker(HashSet)
//workers包含池中的所有工作线程。仅在持有mainLock时访问。
workers.add(w);
// 获取工作线程数量
int s = workers.size();
//largestPoolSize记录着线程池中出现过的最大线程数量
if (s > largestPoolSize)
// 如果 s比它还要大,则将s赋值给它
largestPoolSize = s;
// worker的添加工作状态改为true
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果worker的添加工作完成
if (workerAdded) {
// 启动线程
t.start();
// 修改线程启动状态
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
// 返回线启动状态
return workerStarted;
注释已经很详细了,这里再说下该方法的思路:
- 首先试图原子地增加线程数,这个过程需要检查ctl的状态,如果检查发现不能创建新worker,返回false。否则自旋CAS增加线程数,直到设置成功。
- 线程数增加成功后,真正创建worker并添加到workers工作集合中。创建worker成功后,启动该工作者线程,返回是否启动成功。如果启动worker失败,需要做回滚操作,从workers中移除该worker,并将wc减1。
为什么要用线程池?
简洁的答两点就行。
-
降低系统资源消耗。
-
提高线程可控性。
如何创建使用线程池?
JDK8提供了五种创建线程池的方法:
1.创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2.(JDK8新增)会根据所需的并发数来动态创建和关闭线程。能够合理的使用CPU进行对任务进行并发操作,所以适合使用在很耗时的任务。
注意返回的是ForkJoinPool对象。
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
什么是ForkJoinPool:
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode) {
this(checkParallelism(parallelism),
checkFactory(factory),
handler,
asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
"ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
使用一个无限队列来保存需要执行的任务,可以传入线程的数量;不传入,则默认使用当前计算机中可用的cpu数量;使用分治法来解决问题,使用fork()和join()来进行调用。
3.创建一个可缓存的线程池,可灵活回收空闲线程,若无可回收,则新建线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4.创建一个单线程的线程池。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
5.创建一个定长线程池,支持定时及周期性任务执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
高频考点:
-
创建线程池的五个方法。
-
线程池的五个状态
-
execute执行过程。
-
runWorker执行过程