线程池
线程池:三大方法、7大参数、4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!
我们需要去优化资源的使用 ===> 池化技术
例如:线程池、JDBC的连接池、内存池、对象池 等等。。。。
资源的创建、销毁十分消耗资源
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。
为什么要使用线程池?
Java的线程池是运用场景最多的并发框架,几乎所有需要异步或者并发执行任务的程序都可以使用线程池。
合理使用线程池能带来的好处:
- 降低资源消耗。 通过重复利用已经创建的线程降低线程创建的和销毁造成的消耗。例如,工作线程Woker会无线循环获取阻塞队列中的任务来执行。
- 提高响应速度。 当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。 - 线程是稀缺资源,Java的线程池可以对线程资源进行统一分配、调优和监控。
1、三大方法
//工具类 Executors 三大方法;
public class Demo01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//线程池用完必须要关闭线程池
try {
for (int i = 1; i <=100 ; i++) {
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+ " ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
源码分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
阿里巴巴Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。
2、七大参数
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(线程池的基本大小):
-
提交一个任务到线程池时,线程池会创建一个新的线程来执行任务。注意: 即使有空闲的基本线程能执行该任务,也会创建新的线程。
-
如果线程池中的线程数已经大于或等于corePoolSize,则不会创建新的线程。
如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
maximumPoolSize(线程池的最大数量): 线程池允许创建的最大线程数。 -
阻塞队列已满,线程数小于maximumPoolSize便可以创建新的线程执行任务。
如果使用*的阻塞队列,该参数没有什么效果。
2. workQueue(工作队列): 用于保存等待执行的任务的阻塞队列。
- ArrayBlockingQueue: 基于数组结构的有界阻塞队列,按FIFO(先进先出)原则对任务进行排序。使用该队列,线程池中能创建的最大线程数为maximumPoolSize。
- LinkedBlockingQueue: 基于链表结构的*阻塞队列,按FIFO(先进先出)原则对任务进行排序,吞吐量高于ArrayBlockingQueue。使用该队列,线程池中能创建的最大线程数为corePoolSize。静态工厂方法 Executor.newFixedThreadPool()使用了这个队列。
- SynchronousQueue: 一个不存储元素的阻塞队列。添加任务的操作必须等到另一个线程的移除操作,否则添加操作一直处于阻塞状态。静态工厂方法 Executor.newCachedThreadPool()使用了这个队列。
- PriorityBlokingQueue: 一个支持优先级的*阻塞队列。使用该队列,线程池中能创建的最大线程数为corePoolSize。
4.keepAliveTime(线程活动保持时间): 线程池的工作线程空闲后,保持存活的时间。如果任务多而且任务的执行时间比较短,可以调大keepAliveTime,提高线程的利用率。
5.unit(线程活动保持时间的单位): 可选单位有DAYS、HOURS、MINUTES、毫秒、微秒、纳秒。
6.handler(饱和策略,或者又称拒绝策略): 当队列和线程池都满了,即线程池饱和了,必须采取一种策略处理提交的新任务。
- AbortPolicy: 无法处理新任务时,直接抛出异常,这是默认策略。
- CallerRunsPolicy:用调用者所在的线程来执行任务。
- DiscardOldestPolicy:丢弃阻塞队列中最靠前的一个任务,并执行当前任务。
- DiscardPolicy: 直接丢弃任务。
7. threadFactory: 构建线程的工厂类
以银行业务来类比我们线程池
具体流程:
- 核心线程数(corePoolSize)(对应我们当值的窗口), 一般请求比较少的时候只有核心线程开着,
- 当核心线程都已被占用了(当值窗口都有人了), 这时新的请求进来, 于是进入我们的阻塞队列(候客区)
- 当请求过多, 阻塞队列也满了 (候客区满是等待的人,开始发牢骚了) , 于是经理打电话叫休假的人来加班, 也就是开启我们其他的窗口, 此时线程开到最大线程数(maximumPoolSize)
- 请求继续增多, 以至于数量超过了最大线程数+ 阻塞队列(所有窗口都有人并且候客区也满了), 这时候经理要在门口维持秩序, 阻止新的客人进来, 也就是启动了拒绝策略
- 当客人们办完事, 陆续开始离场, 于是加班的窗口开始空闲, 但是他们并不会立马走人,而是打起了王者荣耀,看看会不会有多的人过来办理, 当他们打完一局后, 发现没有人来了, 于是准备回家睡觉, 这就是空余线程的存活时间(keepAliveTime), 只有当线程数大于核心线程, 空闲时间超过keepAliveTime, 多余空闲线程会销毁, 直到剩下核心线程数
3、线程池工作流程
一个新的任务到线程池时,线程池的处理流程如下:
ThreadPoolExecutor类具体的处理流程:
线程池的核心实现类是ThreadPoolExecutor类,用来执行提交的任务。因此,任务提交到线程池时,具体的处理流程是由ThreadPoolExecutor类的execute()方法去完成的。
- 在创建了线程池后,等待提交过来的任务请求。
- 当调用execute()方法添加一个请求任务时,线程池会做如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。 - 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做超过一定的时间(keepAliveTime) 时,线程池会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。