文章中有参考整理其他一些有价值的博客以及官方文档的内容,如有侵权请联系删除。
我们有两种常见的创建线程的方法,一种是继承Thread类,一种是实现Runnable的接口,Thread类其实也是实现了Runnable接口。但是我们创建这两种线程在运行结束后都会被虚拟机销毁,如果线程数量多的话,频繁的创建和销毁线程会大大浪费时间和效率,更重要的是浪费内存,因为正常来说线程执行完毕后死亡,线程对象变成垃圾!那么有没有一种方法能让线程运行完后不立即销毁,而是让线程重复使用,继续执行其他的任务呢?我们使用线程池就能很好地解决这个问题。
Executor的框架图
Executor接口有一个子接口ExecutorService,ExecutorService的实现类为AbstracExecutorService,而ThreadPoolExcutor正是AbstrcExecutorService的子类。ThreadPoolExecutor是线程池的核心类,此类的构造方法如下:
// 五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) ;
// 六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) ;
// 六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) ;
// 七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
构造方法的参数及意义:
corePoolSize : 线程池的核心池大小,在创建线程池之后,线程池默认没有任何线程。
当有任务过来的时候才会去创建创建线程执行任务。换个说法,线程池创建之后,线程池中的线程数为0,当任务过来就会创建一个线程去执行,直到线程数达到corePoolSize 之后,就会被到达的任务放在队列中。(注意是到达的任务)。换句更精炼的话:corePoolSize 表示允许线程池中允许同时运行的最大线程数。
maximumPoolSize : 线程池能创建最大的线程数量。如果核心线程池和缓存队列都已经满了,新的任务进来就会创建新的线程来执行。但是数量不能超过maximunPoolSize,否侧会采取拒绝接受任务策略。
keepAliveTime:表示线程没有任务时最多保持多久然后停止。默认情况下,只有线程池中线程数大于corePoolSize 时,keepAliveTime 才会起作用。换句话说,当线程池中的线程数大于corePoolSize,并且一个线程空闲时间达到了keepAliveTime,那么就是shutdown。
unit :时间单位,和keepAliveTime配合使用。
TimeUnit是一个枚举类型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天
workQueue :一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能。
常用的workQueue类型:
有界任务队列ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小。
无界任务队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE。
直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
threadFactory :线程工厂,用来创建线程,这是一个接口,new的时候需要实现他的Thread newThread(Runnable r)方法,一般用不上。
handler:拒绝处理策略,线程数量大于最大线程数就会采用拒绝处理策略,四种策略为
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不做任何处理。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务,只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不
会真的丢弃任务,但任务提交线程的性能极有可能会急剧下降。
线程池实现原理:
首先来看最核心的execute方法,这个方法在AbstractExecutorService中并没有实现,从Executor接口,直到ThreadPoolExecutor才实现了该方法,
ExecutorService中的submit(),invokeAll(),invokeAny()都是调用的execute方法,所以execute是核心中的核心,源码分析将围绕它逐步展开。
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.
* 如果正在运行的线程数小于corePoolSize,那么将调用addWorker 方法来创建一个新的线程,并将该任务作为新线程的第一个任务来执行。
当然,在创建线程之前会做原子性质的检查,如果条件不允许,则不创建线程来执行任务,并返回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.
如果任务无法入队列(队列满了),那么我们将尝试新开启一个线程(从corepoolsize到扩充到maximum),如果失败了,那么可以确定原因,
要么是线程池关闭了或者饱和了(达到maximum),所以我们执行拒绝策略。
*/
// 1.当前线程数量小于corePoolSize,则创建并启动线程。
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
// 成功,则返回
return;
c = ctl.get();
}
// 2.步骤1失败,则尝试进入阻塞队列,
if (isRunning(c) && workQueue.offer(command)) {
// 入队列成功,检查线程池状态,如果状态部署RUNNING而且remove成功,则拒绝任务
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果当前worker数量为0,通过addWorker(null, false)创建一个线程,其任务为null
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3. 步骤1和2失败,则尝试将线程池的数量有corePoolSize扩充至maxPoolSize,如果失败,则拒绝任务
else if (!addWorker(command, false))
reject(command);
}
相信看了源码也是一脸蒙蔽,下面用更直观的流程图来讲解,它究竟干了什么事。
结合上面的流程图来逐行解析,首先前面进行空指针检查,
wonrkerCountOf()方法能够取得当前线程池中的线程的总数,取得当前线程数与核心池大小比较:
1.如果小于,将通过addWorker()方法调度执行。
2.如果大于核心池大小,那么就提交到等待队列。
3.如果进入等待队列失败,则会将任务直接提交给线程池。
4.如果线程数达到最大线程数,那么就提交失败,执行拒绝策略。
execute()方法中添加任务的方式是使用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.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 内层的循环,任务是将worker数量加1
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// worker加1后,接下来将woker添加到HashSet<Worker>中,并启动worker
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
final ReentrantLock mainLock = this.mainLock;
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
int rs = runStateOf(c);
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果往HashSet<Worker>添加成功,则启动该线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
addWorker(Runnable firstTask, boolean core)的主要任务是创建并启动线程。它会根据当前线程的状态和给定的值(core or maximum)来判断是否可以创建一个线程。
线程池的任务处理策略:
如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务。
如果当前线程池中的线程数目大于等于corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理.
如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
线程池的关闭:
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。
常见的四种线程池:
除了直接使用ThreadPoolExecutor构造线程池,也可以使用Executors类中提供的几个静态方法来创建线程池:
1.Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。
2.Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池
单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。
3.Executors.newFixedThreadPool(int var0); //创建固定容量大小的缓冲池
该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue,迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。
4.Executors.newScheduledThreadPool(int var0); //创建一个大小无限的线程池
定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
其实四种线程池都是通过 ThreadPoolExecutor 来实现的,只是创建参数不同。