线程池的核心原理篇
线程池的作用
线程池有很多积极的作用,线程池能够提高系统资源的利用率,比如可以重复利用执行完当前任务的线程来执行其他任务,提高线程的复用率;同时线程池能够提高任务的响应速度。不需要每次执行任务时都重新创建线程,当任务到达时不需要等待创建线程就能执行任务。
线程池的状态
线程池在运行的过程中会涉及到几种状态的转换:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED等,它们位于线程池核心类ThreadPoolExecutor下,接下来对几种状态进行分析:
RUNNING:RUNNING状态表示线程池位于运行状态。在此状态下,线程池能够接受新提交的任务,也能够处理阻塞队列中的任务。
SHUTDOWN:SHUTDOWN状态表示线程池处于关闭状态。在此状态下,线程池不能接收新提交的任务,但是不会中断当前执行任务的线程,也能够处理阻塞队列中已经保存的任务。在线程池处于RUNNING状态时,当调用线程池的shutdown()方法时会使线程池处于SHUTDOWN状态。
STOP:SHUTDOWN状态表示线程池处于停止状态。在此状态下,线程池会中断当前正在执行任务的线程,使正在执行的任务被中断;同时线程池不能接收新的任务,也不能处理阻塞队列中保存的任务。在线程池处于RUNNING状态或SHUTDOWN状态时,当调用线程池的shutdownNow()方法时会使线程池处于STOP状态。
TIDYING:TIDYING状态表示线程池中的任务全部执行完毕,活动线程(有效线程)数为0,即线程池中的工作线程为空,阻塞队列为空,线程池中的工作线程数量为0,此时线程池会处于TIDYING状态。
TERMINATED:TERMINATED状态表示终结状态,当线程处于TIDYING状态时,调用线程池的terminated()方法会使线程池进入TERMINATED状态。
线程池的转换过程
线程池中的状态并不是一成不变的,上面介绍了线程池的几种状态,接下来介绍线程池从RUNNING状态转换为TERMINATED状态的流程:
当线程池处于RUNNING状态时,调用线程池中的shutdown()方法会使线程池从RUNNING状态转变为STOP状态;调用线程池中的shutdownNow()方法时会使线程池处于STOP状态;
当线程池处于SHUTDOWN状态时,调用线程池中的shutdownNow()方法时会使线程池处于STOP状态;如果线程池中工作线程数为0,阻塞队列为空,那么会从SHUTDOWN状态转变为TIDYING状态;
当线程处于TIDYING状态时,调用线程池的terminated()方法会使线程池转换为TERMINATED状态。
通过ThreadPoolExecutor类创建线程池
可以通过工具类Executors创建线程池,其实Executors工具类创建线程池大部分调用的是ThreadPoolExecutor类中的构造方法,由于代码长度,ThreadPoolExecutor类中的构造方法截图如下:
ThreadPoolExecutor类创建线程池的核心代码如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//核心线程数<0,最大线程数<=0,最大线程数<核心线程数,保持存活时间<0都是不合法的参数,抛出异常
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
//工作队列为null,线程工厂为null,拒绝策略为null都会抛出空指针异常
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;
}
由上面ThreadPoolExecutor类构造方法的代码可以看出,需要传递七个参数,七个参数的含义如下:
corePoolSize:表示线程池中核心线程的数量;
maximumPoolSize:表示线程池中最大线程数量;
keepAliveTime:针对救急线程的存活时间的变量,就是当线程数量超过线程池中的核心数量时,如果没有新的任务被提交,那么核心线程外的救急线程就会保持在keepAliveTime变量值之后被销毁,而不是立即销毁;
unit:也是针对救急线程,表示keepAliveTime的核心单位;
workQueue:表示线程池中的阻塞队列,阻塞队列中存储着等待执行的任务;
threadFactory:表示用来创建线程的线程工厂。创建线程池的时候,默认的线程工厂创建的线程具有相同的优先级,可以为线程起一个好名字;
handler:表示线程池的拒绝策略。当线程池中的阻塞队列满了的时候,线程数maximumPoolSize也达到了最大值,如果此时再有任务提交到线程池,线程池就会采取策略来拒绝任务的执行。
线程池执行任务的流程
上面介绍了线程池的状态和ThreadPoolExecutor类构造方法的变量,接下来分析向线程池提交任务时,线程池的执行流程:
提交任务时,首先会判断线程池中的线程数是否达到了核心线程数,如果线程池中运行的线程数小于corePoolSize,当向线程池中提交事务时,即使线程池中存在空闲线程,那么也会创建新的线程来执行任务;
当线程池中运行的线程数大于核心线程数corePoolSize同时小于最大线程数maximumPoolSize时,那么会判断工作队列workQueue是否已满,如果没有满,那么将任务添加到队列中等待空闲线程执行;
如果等待队列满了,那么会判断线程池中的线程是否达到了最大线程数maximumPoolSize,如果没有达到,则会创建新的线程执行任务;
如果达到了最大线程数maximumPoolSize,那么线程池会执行拒绝策略来拒绝任务的执行。
线程池的拒绝策略
上面介绍到线程池中的线程数达到了最大线程数,线程池中的workQueue阻塞队列也已经满了,当再有任务提交到线程池中时,线程池会采取拒绝策略来拒绝任务的执行。
线程池的拒绝策略方法在ThreadPoolExecutor类中,其源码如下:
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
在ThreadPoolExecutor类的reject方法中,又调用了handler的rejectedExecution方法,点入此方法得到以下源码:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
在RejectedExecutionHandler接口中,定义了rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法。继续向下看,有四个类实现了RejectedExecutionHandler接口,分别为AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy,DiscardPolicy这四个类。
AbortPolicy策略:此策略为默认的拒绝策略。在拒绝任务时,会直接抛出异常RejectedExecutionException (属于RuntimeException),让你感知到任务被拒绝了,可以根据业务逻辑选择重试或者放弃提交等方法。
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
DiscardPolicy策略:顾名思义,当新任务被提交后直接被丢弃掉,不会给出任何通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
DiscardOldestPolicy策略:顾名思义,该策略会丢掉老的节点。如果线程池没有被关闭,同时没有能力执行任务,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
CallerRunsPolicy策略,相对而言此策略就比较完善了,当有新任务提交后,如果线程池没有被关闭,也没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这种方式会使得新提交的任务不会被抛弃,不会造成损失。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
除了上述的几种拒绝策略,我们也可以自定义我们自己的拒绝策略,如果想使用我们自定义的拒绝策略,只需要与上述几个策略一样,实现RejectedExecutionHandler接口,重写里面rejectedExecution(Runnable r, ThreadPoolExecutor e)的方法即可。
线程池的关闭方法
上文提到过,关闭线程池有两种方式,第一种是调用shutdown方法,第二种是调用shutdownNow方法关闭线程池。
在调用shutdown方法关闭线程池时,线程池不能接收新提交的任务,但是不会中断正在执行任务的线程,同时能够处理阻塞队列中已经保存的任务。待线程池中的任务全部执行完毕。线程池才会关闭。
在调用shutdownNow)方法关闭线程池时,线程池不能接收新提交的任务,也不能继续处理阻塞队列中的任务,同时,还会中断正在执行任务的线程,使得正在执行的任务被中断,线程池立即关闭并抛出异常。
- 点赞
- 收藏
- 关注作者
评论(0)