目录
线程池概述
线程池状态
为什么要创建线程池?
ThreadPoolExcutor线程池的创建
线程池的实现原理
RejetedExecutionHandler:拒绝策略
Executors工具类
Executors工具类创建的线程池有什么弊端呢?
ScheduledThreadPoolExecutor
正确处理线程异常
线程池概述
Java中原生的线程池主要有三种ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。而ThreadPoolExecutor是最古老的一个实现线程池的类了。
线程池状态
ThreadPoolExcutor线程池使用了int的高3位来表示线程池的状态,低29位表示线程的数量。以上信息都存放在原子变量ctl中,目的是为了让线程池与线程个数合二为一,只用通过一次CAS操作完成赋值即可。
状态 | 高3位 | 接收新任务 | 处理阻塞队列中任务 | 说明 |
RUNNING | 111 | 是 | 是 | 线程池处于运行状态 |
SHUTDOWN | 000 | 否 | 是 | 不接收新任务,但是会处理完现有的任务 |
STOP | 001 | 否 | 否 | 立刻中断正在执行的任务,队列中的也全部抛弃 |
TIDYING | 010 | 线程全部执行完毕,活动线程为0,即将进入终结状态 | ||
TERMINATED | 011 | 终结状态 |
为什么要创建线程池?
- 线程在创建以及上下文切换的过程中会产生时间上的消耗,以及资源的浪费,使CPU的性能降低,创建线程池,可以不用反复创建线程,在一开始创建好线程可以反复使用。
- 创建线程池可以根据系统的承受能力,调节线程池中的线程数量,以免对于内存消耗过大导致服务器奔溃。
ThreadPoolExcutor线程池的创建
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
corePoolSize :线程池核心线程数量
maximumPoolSize: 线程池最大线程数量(最大线程数包括核心线程和救急线程)
keepAliverTime :当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间----针对于救急时间
unit :存活时间的单位---针对于救急线程
workQueue :存放任务的队列
handler :超出线程范围和队列容量的任务的处理程序(拒绝策略)
线程池的实现原理
- 调用者提交任务给线程池,如果当前核心线程仍有空闲,那么就创建核心线程来执行任务(如果线程池中的核心线程都被创建过了,只需要重复使用就可以了)。
- 核心线程已满,阻塞队列未满就将任务添加到阻塞队列中。
- 阻塞队列已满,救急线程未满,那么可以用救急线程来执行当前任务,若救急线程都不够用,就可以按照拒绝策略来进行处理。
- 在一段时间内超过corePoolSize的救急线程空闲了,那么由unit和keepAliverTime来决定救急线程的存活时间。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//当前线程数量小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//创建一个线程,并将任务传入
if (addWorker(command, true))
return;
c = ctl.get();
}
//线程数量大于或等于核心线程数,线程池状态为运行状态并且将任务添加至工作队列
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);
}
RejetedExecutionHandler:拒绝策略
当队列和线程池都满了,线程池处于饱和状态,那么就需要拒绝策略来处理新进入无法执行的任务。这个策略默认是是AbortPolicy,表示在饱和状态下,无法执行新任务而抛出异常处理。对于拒绝策略jdk提供了4种实现方式。
- AbortPolicy:让调用者抛出RejectedExecutionException异常
- CallerRunsPolicy:让调用者自己创建一个线程来运行任务
- DiscardPolicy:放弃任务
- DiscardOldestPolicy:放弃队列中最早的任务,让当前任务替换最早的任务
Dubbo中的线程池都使用自定义拒绝策略AbortPolicyWithReport,在抛出RejectedExecutionException异常之前会记录日志,并dump线程栈信息,方便定位。
Netty的线程池又叫做事件循环线程池,即就是EventLoopGroup。其线程池的拒绝策略是创建一个线程来执行任务。
ActiveMQ的线程池拒绝策略的实现是带超时等待(60s),再放入阻塞队列中看是否有空闲。
PinPoint的线程池拒绝策略的实现是一个拒绝策略链,会逐一尝试策略链中的每一个拒绝策略。
Executors工具类
Java中提供了Executors工具类,提供了多种不同种类的线程池,可以应用于多种场景,但都各有利弊。
Executors工具类创建的线程池有什么弊端呢?
- newFixedThreadPool是一个固定大小的线程池,无救急线程,核心线程数等于最大线程数,对于工作队列长度无限制。调用返回一个线程池对象,还可以通过这个对象来进行修改。newSingleThreadExecutor创建的线程池,线程数为1(核心线程),多任务排队执行,当需要线程大于1时,任务就进入工作队列,工作队列是无界的。(如果是自己创建一个线程来串行的执行任务,任务执行失败而终止是没有任何补救措施的,但是线程池会再创建一个线程来执行任务。)他们两个的问题主要在于将任务堆积在没有界限的工作队列中,有可能消耗非常大的内存,甚至是OOM。
- newCachedThreadPool创建的线程池,核心线程数为0,最大线程数为Integer.MAX_VALUE,救急线程的生存时间为60s,全部都为救急线程,且可以无限次创建,工作队列使用的SynchronousQueue,它是没用容量的,有线程来取任务才能放进去任务。newScheduledThreadPool创建的线程池支持定时以及周期性执行任务,需要传入corePoolSize的参数,最大线程数为Integer.MAX_VALUE。newScheduledThreadPool底层实现还是ScheduledThreadPoolExecutor,此线程池keepAliveTime参数为0,工作队列为DelayedWorkQueue(优先队列)。在jdk1.5之前多用Timer来完成,但如果由java.until.Timer来实现定时功能,会有一些问题:
1.Timer单线程运行时,前一个任务执行延迟后会影响到后续任务的执行。
2.当Timer中的任务抛出异常时,会导致后续任务不再执行。
这两个的主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建大量线程占用内存,甚至OOM。
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor可以执行异步的任务,从而接收到执行的结果。ScheduledThreadPoolExecutor实现了ScheduledExecutorService的接口,而ScheduledExecutorService继承了ExecutorService的接口,并新增加了以下几种方法:
1.schedule方法
public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
command:执行的任务 Callable或Runnable接口实现类(使用哪个都可以,但是最好使用Callable可以更好的处理线程异常)
delay:延时执行任务的时间 unit:延迟时间单位
2.scheduleAtFixedRate方法
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,
long period,TimeUnit unit);
command:执行的任务 Callable或Runnable接口实现类
initialDelay:第一次执行任务延迟时间
period:连续执行任务之间的周期,从上一个任务开始执行来计算,如果任务执行了两秒,period只有1s,这1s已经在上个任务执行时消耗了,那么下一个任务等待上一个任务执行完成再执行。
unit:initialDelay和period时间单位
3.scheduleWithFixedDelay方法
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,
long delay,TimeUnit unit);
command:执行的任务 Callable或Runnable接口实现类
initialDelay:第一次执行任务延迟时间
delay:连续执行任务之间的周期,从上一个任务结束后再开始计算。
unit:initialDelay和delay时间单位
正确处理线程异常
任务自己主动的处理异常:
在任务代码可能出现异常的部分加上try/catch代码块进行异常处理。
利用Callable和Futrue进行异常处理:
Callable是有返回值的,返回的是Future对象。用Futrue和Callable进行配合使用,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。Callable就不同了,因为Callable是有返回值的。
使用Callable和Future时,任务执行完成后会返回一个Futrue对象,利用get()方法,如果任务代码中出现异常就会返回异常,无异常就会正常返回。