前言
线程的基础知识以及原理,我们都已经学习了一大部分,线程的确会为我们带来好处,但相对应我们需要付出代价,系统资源换取。当大量的线程创建和销毁,无疑消耗大量的资源。
为了应付这种情况,Java就推出线程池。
线程池
为了降低资源的消耗,提高响应速度,使线程可重复使用。
作用
1. 降低资源消耗,线程其实也是一个对象,既然是对象,那就涉及到堆内存,GC回收等等
2. 提高响应速度,通过从线程池获取比创建相乘,无疑是前者速度快
3. 线程可重复使用,从线程池获取线程且使用完毕后,可放回线程池等待下次使用
说完概念,我们配合源码了解线程池
ThreadPoolExcutor.class
public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//非核心线程最大存活时间
TimeUnit unit,//存活时间单位
BlockingQueue<Runnable> workQueue,//阻塞队列类型/线程池中,等待执行线程队列
ThreadFactory threadFactory,//创建线程工厂
RejectedExecutionHandler handler//饱和策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
从源码可知,创建线程池需要七个参数
七个参数
1. corePoolSize:核心线程数量,线程池创建完毕后,并不是马上创建核心线程,而是接到任务后才创建
2. maximumPoolSize:最大线程数,最大线程数 = 核心线程数 + 非核心线程数
3. keepAliceTime:非核心线程最大空闲/存活时间
4. TimeUnit:时间单位
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
5. BlockingQueue<Runnable> workQueue:阻塞队列/等待任务执行队列
当核心线程都被占用时,就会把任务存放到该队列中,等待被执行,总共有四种队列类型
SynchronousQueue:该队列并不会存放任务,而是接到任务后,马上通知线程池创建非核心线程来执行任务,故为了不让最大线程数出现爆满异常,maximumPoolSize一般被设置成Integer.MAX
LinkedBlockingQueue: 该队列接收到任务后,就会存放该任务到队列中,由于该队列没有最大数量限制,故maximumPoolSize等于无效
ArrayBlockingQueue:该队列接收到任务后,就会存放该任务到队列中,如果队列满时,就会创建非核心线程,但如果达到最大线程数时,就会根据不同饱和策略,做出不同的处理
DelayQueue:该队列的元素必须实现Delayed接口,进入该队列的任务,是达到指定的时间才会执行任务
队列是否有最大值 | maximumPoolSize | 备注 | |
SynchronousQueue | 0 | 有效,但一般设置为:Integer.MAX | 由于一旦该队列接收到任务,就会马上交回线程池处理,所以为了防止出现最大线程数异常,所以设置为Integer.MAX |
LinkedBlockingQueue | 无 | 无效 | 由于核心线程数满时,就会存入该队列中,但该队列没有最大值,所以变相等于maximumPoolSize无效 |
ArrayBlockingQueue | 有 | 自定义 | 由于该队列有最大值,当队列满时,就会创建非核心线程数,当线程数达到最大值时,就会根据饱和政策做出对应的处理 |
DelayQueue | 有 | 自定义 |
6. threadFactory:顾名思义,创建线程的工厂
可以使用线程工厂给每个创建的线程自定义名字,一般情况下无序设置该参数
7. RejectedExecutionHandler handler:饱和策略
当线程池出现异常时,不同的处理方式/机制
AboardPolicy:线程池的默认值,抛出异常
CallerRunsPolicy:线程池里的线程都被占用时,就会调用该线程池的调用者,来处理该任务
DiscaradPolicy:抛弃该任务
DiscaradOldPolicy:表示抛弃最近的任务,并执行当前任务
核心线程,非核心线程,阻塞队列,饱和策略这些概念和名词,第一次听到都会感觉难以理解,打个比喻的话
比喻
- 线程任务=需求
- 核心线程=公司正式员工
- 非核心线程=公司外包员工
- 最大线程数=公司正式员工+公司外包员工
- 队列=需求池
- 饱和政策=需求池>(公司正式员工+公司外包员工)时的,应急方案
1. 当产品经理提出需求时,查看正式员工是否有空闲,有就直接交给他处理,没有则进入第二步
2. 查看需求池是否已满,没有则把新需求放入需求池中,等待正式员工处理,如果满了,则进入第三部
3. 当正式员工和需求池都已经满时,则需要聘用外包人员进行开发处理
3.1 当聘用的外包人员和正式员工没有超出公司规定数时,且能完全处理需求,则直接执行任务
3.2 当聘用的外包人员和正式员工超出公司规定数时,而需求池的需求没能完全处理,就会直接根据应急方法处理
3.3 当聘用的外包人员处理完需求时,且没有需求落入他们手中,就会解雇他们(这也暗示出外包人员的可悲)
线程池和相关的概念已经说完,说说Java自身提供的几种固定线程池模式(但阿里的JAVA规范手册中,不建议直接使用,建议自身创建使用)
Java提供的固定线程池模式
FixedThreadPool
提供可重复的固定线程池,超出的线程池会在队列中等待,源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看出,核心线程数=最大线程数,使用的是LinkedBlockingQueue队列,使用默认的AboardPolicy政策
由于使用LinkedBlockingQueue队列,该队列没有最大值,故keepAliveTime是无效的,设置成0,因为根本不会创建非核心线程。
CachedThreadPool
一个没有核心线程,最大线程数=Integer.MAX的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心线程=0,即全部都是非核心线程,并且没有上限。
keepAliveTime是60秒,就是说空闲线程等待新任务60秒,超时则销毁。此处用到的队列是阻塞队列
SynchronousQueue,这个队列没有缓冲区,所以其中最多只能存在一个元素,有新的任务则阻塞等待。
SingleThreadExecutor
核心线程和最大线程数=1,且采用LinkedBlockingQueue队列
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}