前言
何谓线程池
- 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。
- 线程池不仅能够保证CPU内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。
- 即线程池的线程数不是越多好。
线程池的应用范围
- 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大。
- 但对于长时间的任务,线程池的优点就不明显了。因为线程创建后不能复用,会导致大量任务阻塞,没有线程可以执行它。
- 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
- 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现”内存溢出(OOM)”的错误。
Java 线程池
线程池起源于Executor接口
- Executor接口其中一个分支,如下图:
线程池的使用
代码样例
public class ExecutorTest2 {
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(8,10,0, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(5), new ThreadPoolExecutor.AbortPolicy());//创建线程池
Runnable runnable = ()-> {//runable 任务的内容
try {
//System.out.println(executorService);
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
};
IntStream.range(0,9).forEach(i-> executorService.submit(runnable));//像线程池提交任务,提交9个
executorService.shutdown();
}
}
复制代码
创建线程池
有一个Executors类,它有工厂方法,可以用来创建线程池,但在实际应用中并不使用,不赘述。
ThreadPoolExecutor的构造方法
参数解释
参数:
- corePoolSize – 要保留在池中的核心线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
- maximumPoolSize – 池中允许的最大线程数
- keepAliveTime – 当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间。
- unit – keepAliveTime参数的时间单位
- workQueue – 用于在执行任务之前保存任务的队列。 这个队列将只保存execute方法提交的Runnable任务。
- threadFactory – 执行程序创建新线程时使用的工厂
- handler – 任务无法放进线程池时使用的处理程序,因为达到了线程maximumPoolSize和队列(workQueue)容量
抛出:
IllegalArgumentException
- 如果以下情况之一成立:
- corePoolSize < 0
keepAliveTime < 0
maximumPoolSize <= 0
maximumPoolSize < corePoolSize
- corePoolSize < 0
NullPointerException
- 如果workQueue或threadFactory或handler为 null
关于线程池的抽象和执行的流程
- 抽象
- 流程
关于线程池的总体执行策略:
- 如果线程池中正在执行的线程数 < corePoolSize,那么线程池就会优先选择创建新的线程而非将提交的任务加到阻塞队列(workQueue)中。
- 如果线程池中正在执行的线程数 >= corePoolSize,那么线程池就会优先选择对提交的任务进行阻塞排队而非创建新的线程。
- 如果提交的任务无法加入到阻塞队列当中,那么线程池就会创建新的线程;如果创建的线程数超过了maximumPoolSize,那么拒绝策略(handler)就会起作用。
又到了喜闻乐见的阅读jdk源码环节
拒绝策略
- AbortPolicy
- DiscardPolicy
- DiscardOldestPolicy
- CallerRunsPolicy
其实,一般应用中也不用这些jdk自带的拒绝策略
一般由程序员根据实际情况实现RejectedExecutionHandler接口
submit方法
- submit的机制
关于线程池任务提交的总结:
-
两种提交方式: submit与execute。
-
submit有三种方式,无论哪种方式,最终都是将传递进来的任务转换为一个callable对象进行处理。
-
当callable对象构造完毕后,最终都会调用Executor接口中声明的execute方法进行统一的处理。
线程池的状态
线程池一共存在5种状态:
- RUNNING︰线程池可以接收新的任务提交,并且还可以正常处理阻塞队列中的任务。
- SHUTDOWN︰不再接收新的任务提交,不过线程池可以继续处理阻塞队列中的任务。
- STOP:不再接收新的任务,同时还会丢弃阻塞队列中的既有任务﹔此外,它还会中断正在处理中的任务。
- TIDYING:所有的任务都执行完毕后(同时也涵盖了阻塞队列中的任务),当前线程池中的活动的线程数量降为0,将会调用terminated方法。
- TERMINATED︰线程池的终止状态,当terminated方法执行完毕后,线程池将会处于该状态之下。
线程池状态的变化并不是连续的,它会视情况转换到相应的状态。并不一定是从RUNNING到SHUTDOWN。
RUNNING -> SHUPDOWN∶当调用了线程池的shutdown方法时,或者当finalize方法被隐式调用后(该方法内部会调用shutdown方法)。
RUNNING,SHUTDOWN -> STOP:当调用了线程池的shutdownNow方法时。
SHUTDOWN ->TIDYING︰在线程池与阻塞队列均变为空时。
STOP ->TIDYING:在线程池变为空时。
TIDYING ->TERMINATED:在terminated方法被执行完毕时。
状态源自jdk源码
如此就可以阅读jdk获知execute方法的实现
关于线程池状态是如何shutdown的,jdk源码后续阅读
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END