内容目录:
线程池
正文:
上一篇博文中我们简单的介绍了 Condition接口的使用,现在简单的回顾一下,Condition接口可以挂起程序,到目前为止我们对于线程有了较为全面的了解,后面的博文我们会介绍与线程相关的一些内容,这些内容对线程性能的提升起到很大的帮助。本篇我们介绍多线程中我们常用的线程池。由于线程池在配置和使用过程中略为复杂,所以本章我们将会花费较大的篇幅来讨论线程池的使用
线程池的基本概念
1、什么是线程池?2、线程池的作用 ? 3、怎样使用线程池?4、线程池的工作原理?5、线程池的组成部分?6、线程池的配置与优化
1、什么是线程池?
线程池:我们类比池塘,池塘里面有很多鱼,池塘是一个"容器"用来管理鱼,那么线程池也可以进行这样的类比,线程池也是一个容器可以对线程的数量进行控制和管理同时对空闲的线程进行回收
2、线程池的作用
1、管理线程数量 2、回收空闲线程再利用 从而减少线程创建和销毁所带来的开销,提升系统工作效率
3、怎样使用线程池
1、学习ThreadPoolExcutor类,因为这是线程池的核心类
一、构造函数
Constructor and Description |
---|
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
创建一个新的
ThreadPoolExecutor 与给定的初始参数和默认线程工厂和拒绝执行处理程序。
|
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
创建一个新的
ThreadPoolExecutor 与给定的初始参数和默认线程工厂。
|
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
创建一个新的
ThreadPoolExecutor 与给定的初始参数和默认拒绝执行处理程序。
|
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
创建一个新
ThreadPoolExecutor 给定的初始参数。
|
前面3个构造函数都是通过调用第四个构造函数来实现的,解释第四个构造函数中所有的参数的意义
int corePoolSize -- 核心线程池大小设置,在线程池初始化时,线程池中的线程数量为0,只有等到任务来了之后,才会创建线程去执行任务,当池中的线程数量达到corePoolSize后就会将任务放入缓存队列中
int maximumPoolSize -- 线程池中线程最大线程数,表示线程池中最大能创建多少个线程
long keepAliveTime -- 在没有任务执行的情况下线程最大的保持时间
TimeUnit unit -- 参数keepAliveTime的单位时间,可从以下取值
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小时 TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //纳秒
BlockingQueue<Runnable> workQueue -- 一个线程阻塞队列,用来存储等待执行的任务,具体我们在后期会讲解,可以选用以下的数据类型
ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue;
ThreadFactory threadFactory -- 线程工厂类,主要通过工厂模式来创建线程
RejectedExecutionHandler handler -- 拒绝处理任务时的策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
二、方法详解
首先我看一下ThreadPoolExecutor的层级关系
interface Executor --> execute(Runnable target)
|
interface ExecutorService
|
abstract AbstractExecutorService
|
class ThreadPoolExecutor
由此我们可以看出,最顶层的Executor接口,它只有一个execute(Runnable target)函数,表示执行具体的任务;
然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
然后ThreadPoolExecutor继承了类AbstractExecutorService
ThreadPoolExecutor在实现了AbstractExecutorService的基础上又添加了许多新的函数
public void allowCoreThreadTimeOut(boolean value)--设置是否允许核心线程超时,在前面构造函数中我提到了一个参数keepAliveTime即核心线程在没有任务的情况下保存的时间,默认情况下,只有当池中的线程数量大于corePoolSize时,keepAliveTime才会起作用,但是如果调用了此函数则表示即使没有大于corePoolSize时,keepAliveTime也会起作用,即当没有任务时设置的时间长度将会在下一个任务来后被替换
public boolean allowCoreThreadTimeOut();
public boolean awaitTermination(long timeOut , TimeUnit unit);
public int getActiveCount();
public long getCompletedTaskCount();
public int getCorePoolSize();
public long getKeepAliveTime(TimeUnit unit);
public int getLargestPoolSize();
public int getMaximumPoolSize();
public int getPoolSize();
public volid shutdown();
public List<Runnable> shutDownNow();
4、通过以上的介绍我们对线程池的核心类有了一个大致的了解,接下来我们来具体讲一下线程池的工作原理,我们从以下几个方面进行
1、线程池状态 2、任务的执行 3、线程池中的线程初始化 4、任务缓存队列以及排队策略 5、线程池的关闭 6、线程池容量的动态调整
1、线程池状态
ThreadPoolExecutor中用volatile定义了一个变量
volatile int runState; --表示当前线程的运行的状态,使用volatile保证可见性
static final int RUNNING = 0; -- 初始化时,线程池处于RUNNING状态
static final int SHUTDOWN = 1; -- 如果调用了shutdown()函数,则线程池的状态为SHUTDOWN不在接受新的任务,它会等所有的任务执行完成后,关闭线程池;
static final int STOP = 2; -- 如果调用了shutDownNow()函数,则线程池的状态变成为STOP,线程池不再接受新的任务,并且会立即停止掉当前正在执行的任务,慎用!
static final int TERMINATED = 3; -- 当线程池处于STOP或者SHUTDOWN状态的时候,并且所有线程已经销毁,任务缓存队列中已经清空,则线程池的状态为TERMINATED
2、任务的执行
这里我先把结论写出来,然后我们从结论来往前推
1、如果当前线程池中的线程数量小于corePoolSize,则每来一个任务就创建一个线程去处理
2、如果当前线程池中的线程数量大于corePoolSize,则将任务放入到缓存队列中,如果添加成功,则等待空闲的线程去执行任务;如果没有添加成功,则试图去创建线程去执行任务
3、如果线程池的线程数量达到了maximumPoolSize则会执行拒绝任务策略
4、如果池中的线程数量大于corePoolSize并且超过keepAliveTime设置的时间,则线程会终止,直到线程数量少于corePoolSize
3、线程池中线程的初始化
默认情况下,线程池的创建后,池中是没有线程的,可以选择使用下面的两个函数来进行初始化线程数量
prestartCoreThread();--初始化核心线程
prestartAllCoreThread(); -- 初始化所有核心线程
4、 任务缓存队列和排队策略
任务缓存队列即就是workQueue,类型为BlockingQueue<Runnable>,通常可以取下面三种类型
1)、ArrayBlockingQueue:基于数组的先进先出队列,也叫做有界队列,需要提供一个初始化大小
2)、LinkedBlockingQueue:基于链表的先进先出队列,也叫做无界队列,默认为Integer.Max_value
3)、synchronousQueue: 这个队列不会保存任务,会直接创建一个线程来处理任务
5、任务拒绝策略
线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
6、线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
7、 线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
- setCorePoolSize:设置核心池大小
- setMaximumPoolSize:设置线程池最大能创建的线程数目大小
8、线程池的配置与优化
线程池的优化更多的就是选择最合适大小的线程池,来处理任务,太大浪费资源,太小任务处理不过来
如何合理配置线程池大小,仅供参考。
一般需要根据任务的类型来配置线程池大小:
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
如果是IO密集型任务,参考值可以设置为2*NCPU
当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
本文内容参考 "https://www.cnblogs.com/exe19/p/5359885.html"