三、线程池原理:
管理同构线程的资源池。实现线程复用,线程处理完一个任务不被销毁,可以继续处理下一个任务;
为什么要用线程池呢??
- 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率;
- 避免线程并发数量过多,抢占系统资源从而导致阻塞
- 对线程进行简单管理,如延时执行、定时循环执行的策略等;
1.线程池实现 Executor这个接口,具体实现为ThreadPoolExecutor类(java.uitl.concurrent.ThreadPoolExecutor)
完整的实现继承是这样的:
Executor这个接口 --->ExecutorService接口--->AbstractExecutorService抽象类--->ThreadPoolExecutor类;
2.ThreadPoolExecutor类有四个构造函数,通过配置参数来配置线程池:
//五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
//六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
//六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
//七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
/**
* 核心线程数为 (corePoolSize),
* 最大线程数为(maximumPoolSize),
* 存活时间(keepAliveTime),
* 工作队列为BlockingQueue<Runnable>(workQueue),
* 线程工厂为默认的ThreadFactory (threadFactory),
* 饱和策略(拒绝策略)为RejectedExecutionHandler : 抛出异常(handler).
*/
3.线程池基本参数概念:
- 核心线程:(不干活我也养你)线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程,核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
- corePoolSize:在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
- maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程;
- keepAliveTime:表示线程没有任务执行时最多保持多久会终止.(默认情况 当线程数大于corePoolSize 该参数才会起效)
- 工作队列:workQueue:一个阻塞队列,用来存储等待执行的任务;
- 饱和策略:handler:表示当拒绝处理任务时的策略
- .线程工厂:threadFactory,主要用来创建线程
4.工作队列:workQueue:一个阻塞队列,用来存储等待执行的任务;
如果新请求的到达速率超过了线程池的处理速率,那么新到来的请求将累积起来。在线程池中,这些请求会在一个由Executor管理的Runnable队列中等待,而不会像线程那样去竞争CPU资源..
workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
- 1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
- 2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
- 3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
5.饱和策略handler:表示当拒绝处理任务时的策略,有以下四种取值:
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
1 2 3 4 |
|
6.线程工厂:threadFactory,主要用来创建线程;
在ThreadFactory中只定义了一个方法newThread,每当线程池需要创建一个新线程时都会调用这个方法;
7.线程池的关闭:
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
8.线程池容量的动态调整:
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
- setCorePoolSize:设置核心池大小
- setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务
9.线程池执行流程:
- 默认情况下创建线程池后不会立即创建线程,等到有任务提交时会创建线程((除非调用prestartCoreThread或prestartAllCoreThreads方法) );
- 当创建的线程数<corePoolSize时,每来一个一个任务,线程工厂都会创建一个线程取执行它;即使有现成空闲, 直到线程数达corePoolSize;
- 当创建的线程数 =corePoolSize时,再提交的任务就进入工作队列等待,当有线程空闲时 就会去等待队列取任务,继续执行;
- 当线程数=corePoolSize 且等待队列也满了,如果这个时候还提交任务,则会继续创建线程来处理,直到线程数达到最大线程数maximumPoolSize;
- 达到了线程池的maximumPoolSize 并且等待队列也满了,如果这个时候还提交任务,此时就要采取一些饱和策略(拒绝接受新任务 或者抛弃等等待队列中的一些任务)异常;
- 如果某个线程的控线时间超过了keepAliveTime,那么将被标记为可回收的,并且当前线程池的当前大小超过了核心线程数时,这个线程将被终止
10.模拟线程池内部机制:
- 假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。
- 因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;
- 当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;
- 如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;
- 然后就将任务也分配给这4个临时工人做;
- 如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。
- 当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。
这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。
<1>向ThreadPoolExecutor添加任务:
通过ThreadPoolExecutor.execute(Runnable command)
方法即可向线程池内添加一个任务
<2>ThreadPoolExecutor的策略,当一个任务被添加进线程池时有以下情况:
- 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
- 线程数量达到了corePools,则将任务移入队列等待
- 队列已满,新建线程(非核心线程)执行任务
- 队列已满,总线程数又达到了maximumPoolSize,就会由handler (RejectedExecutionHandler)抛出异常
四、四种常用的线程池:
1.FixedThreadPool():定长线程池(正规):
- 可控制线程最大并发数(同时执行的线程数)
- 超出的线程会在队列中等待
- 有指定的线程数的线程池,有核心的线程,里面有固定的线程数量,响应的速度快。正规的并发线程,多用于服务器。固定的线程数由系统资源设置。
核心线程是没有超时机制的,队列大小没有限制,除非线程池关闭了核心线程才会被回收
创建方法:
//nThreads => 最大线程数即maximumPoolSize ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);
2.CachedThreadPool():可缓存线程池:
- 线程数无限制
- 有空闲线程则复用空闲线程,若无空闲线程则新建线程
- 一定程序减少频繁创建/销毁线程,减少系统开销
- 只有非核心线程,最大线程数很大(Int.Max(values)),它会为每一个任务添加一个新的线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收。
- 缺点就是没有考虑到系统的实际内存大小。
创建方法:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
3.SingleThreadExecutor():单线程化的线程池:
- 有且仅有一个工作线程执行任务
- 所有任务按照指定顺序执行,即遵循队列的入队出队规则((FIFO, LIFO, 优先级)
- 只有一个核心线程,就是一个孤家寡人,通过指定的顺序将任务一个个丢到线程,都乖乖的排队等待执行,不处理并发的操作,不会被回收。确定就是一个人干活效率慢。
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();
4.ScheduledThreadPool():定长线程池
- 有延迟执行和周期重复执行的线程池。
- 它的核心线程池固定,非核心线程的数量没有限制,但是闲置时会立即会被回收。
创建方法:
//nThreads => 最大线程数即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
参考:
https://blog.youkuaiyun.com/u011531613/article/details/61921473
https://blog.youkuaiyun.com/v123411739/article/details/79124193