本文学习内容
目录
线程池概述
ThreadPoolExecutor 的实现原理
继承关系
属性
阻塞队列
拒绝策略
构造方法
ThreadPoolExector 类的几个重要方法
任务的提交
关闭线程池
shutdown()
shutdownNow()
相关文章
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
线程池实现了线程的复用,使得线程在一个任务执行完成之后并不销毁,而是去执行另一个任务。
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
我们应该如何创建一个线程池? Java中已经提供了创建线程池的一个类:Executor,我们一般用它的子类ThreadPoolExecutor 来创建线程池
ThreadPoolExecutor 继承AbstractExecutorService 抽象类,AbstractExecutorService 抽象类实现了ExecutorService 接口,ExecutorService 接口又继承了Executor。
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的。
ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等。
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
然后ThreadPoolExecutor继承了类AbstractExecutorService。
//线程状态
volatile int runState;
static final int RUNNING = 0;
static final int SHUTDOWN = 1;
static final int STOP = 2;
static final int TERMINATED = 3;
//阻塞队列
private final BlockingQueue workQueue;
//互斥锁
private final ReentrantLock mainLock = new ReentrantLock();
//终止条件
private final Condition termination = mainLock.newCondition();
//线程集合,一个worker 对象为一个线程
private final HashSet workers = new HashSet();
//空闲状态
private volatile long keepAliveTime;
//表示是否允许“线程在空闲状态时,仍能够存活”
private volatile boolean allowCoreThreadTimeOut;
//核心池大小
private volatile int corePoolSize;
//最大池大小
private volatile int maximumPoolSize;
//当前大小
private volatile int poolSize;
//拒绝处理任务时的策略
private volatile RejectedExecutionHandler handler;
//线程工厂
private volatile ThreadFactory threadFactory;
//记录之前线程最大时的大小
private int largestPoolSize;
//完成线程的数量
private long completedTaskCount;
//默认处理器。抛出异常
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
在这里大部分属性我们都能一眼看出来它的作用,我们重点看一下他的阻塞队列和拒绝策略。
用来存储待执行的任务,这个参数的选择也很重要,对线程池的运行过程产生重大的影响。
ArrayBlockingQueue
基于数组的先进先出队列,此队列创建时必须指定大小。
public ArrayBlockingQueue(int capacity, boolean fair,
Collection extends E> c) {
this(capacity, fair);
if (capacity < c.size())
throw new IllegalArgumentException();
for (Iterator extends E> it = c.iterator(); it.hasNext();)
add(it.next());
}
LinkedBlockingQueue
基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_Vlaue.
public LinkedBlockingQueue(Collection extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
enqueue(e);
++n;
}
count.set(n);
} finally {
putLock.unlock();
}
}
SychronousQueue
这个队列比较特殊,它不会保存提交的任务 ,而是直接创建了一个线程去执行新来的任务.
Handler:表示当拒绝处理任务时的策略。一共有四种策略。
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
可以自己实现RejectedExecutionHandler接口,并且实现其方法即可
ThreadPoolExecutor 有四个构造方法,但其实前三个构造方法都调用的是第四个构造器,所以我们主要看一下最后一个构造器。
public ThreadPoolExecutor(int corePoolSize,//核心线程数量
int maximumPoolSize,//最大线程数量
long keepAliveTime,//超出核心线程以外线程的存活时间
TimeUnit unit,//参数keepAliveTime的时间单位
BlockingQueue 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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
我们分析一下它的参数:
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime
表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize
但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:keepAliveTime 的单位,
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue:阻塞队列。
threadFactory:线程工厂,用来创建线程。
handler: 当任务被拒绝时的策略。
我们用一张图来看一下核心线程数、最大线程数和阻塞队列的关系。
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
executor()是ThreadPoolExector 的核心方法,我们来重点分析一下这个方法。
public void execute(Runnable command) {
//线程为空,抛出异常
if (command == null)
throw new NullPointerException();
//判断当前线程数是否大于核心线程数,若不大于,则执行第二个条件,将它加入核心线程里面。
// 加入失败,进入下面的代码块
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
//判断当前线程是否处于运行状态,并且将任务加入阻塞队列
if (runState == RUNNING && workQueue.offer(command)) {
//句判断是为了防止在将此任务添加进任务缓存队列的同时其他线程突然调用shutdown
//或者shutdownNow方法关闭了线程池的一种应急措施。
if (runState != RUNNING || poolSize == 0)
//保证 添加到任务缓存队列中的任务得到处理
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command);
}
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))
判断当前线程数是否大于核心线程数,如果不大于就执行addIfUnderCorePoolSize(command)方法。我们来分析一下这个方法。
若这个方法执行成功,则任务提交成功。
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//当前线程数小于核心线程数,当前线程处于运行状态
if (poolSize < corePoolSize && runState == RUNNING)
t = addThread(firstTask);//创建线程去执行firstTask任务。
} finally {
mainLock.unlock();
}
return t != null;
}
这里有的人可能要问,在进入这个方法之前不是已经判断过poolSize < corePoolSize了嘛,但事实是在前面我们判断的时候没有加锁,也许在我们运行完上一个语句后就有其他任务提交进去了,导致核心线程满了,所以这里需要再判断一下。让我们看一下是怎么创建线程的。
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
//创建一个线程,执行任务
Thread t = threadFactory.newThread(w);
boolean workerStarted = false;
if (t != null) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
w.thread = t;//将创建的线程的引用赋值为w的成员变量
workers.add(w);
int nt = ++poolSize; //当前线程数加1
if (nt > largestPoolSize)
largestPoolSize = nt;
try {
t.start();
workerStarted = true;
}
finally {
if (!workerStarted)
workers.remove(w);
}
}
return t;
}
如果当前线程大于核心线程数就执行括号里的内容,也就是下面这句。
if (runState == RUNNING && workQueue.offer(command))
判断当前线程池是否处于运行状态,如果处于运行状态就执行workQueue.offer(command) 方法,这个方法是向阻塞队列里面提交任务。加入阻塞队列成功后就执行下面的if语句。
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
这里要先判断线程池是不是处于运行状态,和上面一样,也是因为前面的操作没有加锁,为了防止在运行过程中其它线程关闭了线程池。ensureQueuedTaskHandled(command)是为了确保添加到任务缓存队列中的任务得到处理。
我们再回到f (runState == RUNNING && workQueue.offer(command)) 语句,当加入阻塞队列失败,我们就运行下面的else 语句的内容,也就是运行addIfUnderMaximumPoolSize(command)。
else if (!addIfUnderMaximumPoolSize(command))
reject(command);
private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//判断当前线程是否小于最大线程数,如果小于则判断线程是否处于运行状态
if (poolSize < maximumPoolSize && runState == RUNNING)
//创建线程去执行firstTask任务。
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
return t != null;
}
这个方法和addIfUnderCorePoolSize 方法大同小异,可以参考一下,此时若这个方法执行成功,则任务提交成功,否则采取拒绝策略。
从executor方法中,我们大概能梳理出这个提交流程:
shutdown()和shutdownNow()是用来关闭线程池的。
shutdown并不是直接关闭线程池,而是不再接受新的任务...如果线程池内有任务,那么把这些任务执行完毕后,关闭线程池
//关闭线程池:不管其他线程有没有结束,都关闭
public void shutdown() {
//安全管理器
SecurityManager security = System.getSecurityManager();
///判断调用者是否有权限shutdown线程池
if (security != null)
security.checkPermission(shutdownPerm);
//互斥锁
final ReentrantLock mainLock = this.mainLock;
//加锁
mainLock.lock();
try {
if (security != null) {
for (Worker w : workers)
security.checkAccess(w.thread);
}
//运行状态
int state = runState;
//修改状态
if (state < SHUTDOWN)
runState = SHUTDOWN;
//遍历线程并停止
try {
for (Worker w : workers) {
w.interruptIfIdle();
}
} catch (SecurityException se) {
runState = state;
throw se;
}
//尝试终止线程池
tryTerminate();
} finally {
mainLock.unlock();
}
}
public List shutdownNow() {
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkPermission(shutdownPerm);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (security != null) { // Check if caller can modify our threads
for (Worker w : workers)
security.checkAccess(w.thread);
}
//线程状态
int state = runState;
//修改状态
if (state < STOP)
runState = STOP;
//遍历线程并中断
try {
for (Worker w : workers) {
w.interruptNow();
}
} catch (SecurityException se) {
runState = state;
throw se;
}
List tasks = drainQueue();
//尝试终止线程池
tryTerminate();
return tasks;
} finally {
mainLock.unlock();
}
}
这个方法表示不再接受新的任务,并把任务队列中的任务直接移出掉,如果有正在执行的,尝试进行停止...
四种常见线程池