目录
0,Java 线程状态转换
1,Java 线程池的三种创建方式
newCacheThreadPool()
:核心线程数是0
,非核心线程数是2^31 - 1
,没有阻塞队列(不存放任务)- 适合任务数比较密集,但每个任务执行时间较短的情况
newFixedThreadPool(n)
:核心线程数是n
,没有非核心线程,阻塞队列最大为2^31 - 1
- 适用于任务量已知,相对耗时的任务
newSingleThreadExecutor()
:核心线程数是1
,没有非核心线程,阻塞队列最大为2^31 - 1
- 适用于多个任务排队执行
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(
0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(
nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到以上三种创建方式实际使用的都是 ThreadPoolExecutor
类,只是参数不同而已。
2,ThreadPoolExecutor 类的原理
- ThreadPoolExecutor:基本的线程池实现
- ScheduledThreadPoolExecutor:任务调度线程池:带有定时任务的线程池
- 在『任务调度线程池』功能加入之前,可以使用
java.util.Timer
来实现定时功能,Timer 的优点在于简单易用 - 但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务
- 在『任务调度线程池』功能加入之前,可以使用
线程池有五种状态:
- RUNNING:正常运行状态,可接收新任务,可处理阻塞队列中的任务
- SHUTDOWN:不会接收新任务,但会处理阻塞队列剩余任务
- STOP:会中断正在执行的任务,并抛弃阻塞队列任务
- TIDYING:任务全执行完毕,活动线程为 0,即将进入终结
- TERMINATED:终结状态
1,构造方法及参数含义
ThreadPoolExecutor
类位于 java.uitl.concurrent
包中,其参数含义如下:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数,核心线程就是一直存在的线程
int maximumPoolSize, // 最大线程数,表示线程池中最多能创建多少个线程
// 非核心线程数 = 最大线程数 - 核心线程数
long keepAliveTime, // 针对非核心线程而言,表示线程没有任务执行时最多保持多久时间会终止
// 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用
// 当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到 keepAliveTime,
// 则会终止,直到线程池中的线程数不超过corePoolSize
// 但是如果调用了 allowCoreThreadTimeOut(boolean) 方法
// 在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用
// 直到线程池中的线程数为 0
TimeUnit unit, // 时间单位,与 keepAliveTime 配合使用,针对非核心线程
BlockingQueue<Runnable> workQueue, // 存放任务的阻塞队列
ThreadFactory threadFactory, // 创建线程的工厂,可以为线程创建时起个好名字
RejectedExecutionHandler handler // 拒绝策略
// 任务太多的时候会进行拒绝操作
// 核心线程,非核心线程,任务队列都放不下时
)
unit
参数有7种取值,在 TimeUni t类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue
参数表示一个阻塞队列,用来存储等待执行的任务,有以下几种选择:
ArrayBlockingQueue
:基于数组的先进先出队列,此队列创建时必须指定大小LinkedBlockingQueue
:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE
SynchronousQueue
:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务
handler
参数表示拒绝策略,当线程池的任务缓存队列已满,并且线程池中的线程数目达到 maximumPoolSize
,如果还有任务到来就会采取任务拒绝策略。
handler
通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy
:丢弃任务并抛出RejectedExecutionException
异常(这是默认策略)ThreadPoolExecutor.DiscardPolicy
:也是丢弃任务,但不抛出异常ThreadPoolExecutor.DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)ThreadPoolExecutor.CallerRunsPolicy
:由调用线程(调用者)处理该任务
2,一些重要方法
ThreadPoolExecutor
类中的几个重要方法:
execute()
:向线程池提交一个任务,交由线程池去执行submit()
:也是向线程池提交任务,但是和execute()方法不同,它能够返回任务执行的结果- 它实际上还是调用的
execute()
方法,只不过它利用了Future
来获取任务执行结果
- 它实际上还是调用的
invokeAll()
:提交一个任务集合invokeAny()
: 提交一个任务集合,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消shutdown()
:关闭线程池,再也不会接受新的任务- 不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止
shutdownNow()
:关闭线程池,再也不会接受新的任务- 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
isShutdown()
:不在 RUNNING 状态的线程池,此方法就返回 trueisTerminated()
:线程池状态是否是 TERMINATED
动态调整线程池的大小:
setCorePoolSize
:设置corePoolSize
setMaximumPoolSize
:设置maximumPoolSize
还有一些方法:
getQueue()
getPoolSize()
getActiveCount()
getCompletedTaskCount()
3,线程池状态
在 ThreadPoolExecutor
中定义了一个 volatile
变量,另外定义了几个 static final
变量表示线程池的各个状态:
volatile int runState; // 当前线程池的状态
static final int RUNNING = 0;
static final int SHUTDOWN = 1;
static final int STOP = 2;
static final int TERMINATED = 3;
线程池的状态变化:
- 当创建线程池后,线程池处于
RUNNING
状态 - 当调用了
shutdown()
方法,则线程池处于SHUTDOWN
状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕 - 当调用了
shutdownNow()
方法,则线程池处于STOP
状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务 - 当线程池处于
SHUTDOWN
或STOP
状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED
状态
4,线程池模型
ThreadPoolExecutor
类构造出来的线程池的模型如下:
大体上由三大部分组成:
- 核心线程:一直存在的线程,数量为
corePoolSize
- 在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务;当线程池中的线程数目达到
corePoolSize
后,就会把到达的任务放到缓存队列当中; - 除非手动调用了
prestartAllCoreThreads()
或者prestartCoreThread()
方法,来预创建线程,即在没有任务到来之前就创建线程。
- 在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务;当线程池中的线程数目达到
- 非核心线程:临时创建的线程,会根据任务的多少来进行创建
- 当然
总的非核心线程的数量
不能大于maximumPoolSize - corePoolSize
- 如果
总的非核心线程的数量
很大,并且任务非常多,就会创建非常多的线程
- 当然
- 任务队列:
SynchronousQueue
:只能存放一个任务LinkedBlockingQueue
:可以无限存放任务,如果任务超级多,会有内存溢出的可能
同一时刻,线程池中所能接纳的最大任务数为:maximumPoolSize + 任务队列的长度;
当超出这个范围后,如果再有新的任务过来,将会被拒绝。
线程池中的线程的初始化:
- 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程
- 在实际中,如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
prestartCoreThread()
:初始化一个核心线程prestartAllCoreThreads()
:初始化所有核心线程
处理任务的流程:
- 如果当前线程池中的线程数目小于
corePoolSize
,每来一个任务,就会创建一个线程去执行这个任务 - 如果当前线程池中的线程数目>=
corePoolSize
,每来一个任务,会尝试将其添加到任务缓存队列当中- 若添加成功,则该任务会等待空闲线程将其取出去执行
- 若添加失败(一般来说是任务缓存队列已满),则会尝试创建临时线程去执行这个任务
- 当核心线程,任务队列,非核心线程都使用完后,如果还有新的任务过来,将会进行拒绝处理
线程复用: 当线程处理完已分配的任务后,在没有销毁之前,还会用于去处理新的任务。这样可以避免创建过多的线程。
3,任务的执行过程
源码部分是 ThreadPoolExecutor
类中的 execute
方法:
流程图如下:
4,合理设置线程池的大小
一般需要根据任务的类型来配置线程池大小:
- 如果是 CPU密集型任务,就需要尽量压榨 CPU,可以设为
CPU个数+1
- 如果是 IO密集型任务,可以设置为
CPU个数*2
这只是一个参考值,具体的设置还需要根据实际情况进行调整,可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。