线程池:
- 是线程的一种使用模式。通过创建一定数量的线程,让他们时刻准备就绪等待新任务的到达,而任务执行结束之后重新回来继续待命。
- 线程池核心的设计思想:复用线程,平摊线程的创建与销毁的开销代价
一、为什么使用线程池?
原因有一下几点:
1. 避免了线程的重复创建与销毁所带来的资源消耗。
2. 提升了任务的响应速度,任务来了直接选一个线程执行而无需等待线程的创建。
3. 线程的统一分配和管理,也方便统一的监控和调优。
二、线程池的构造函数
线程池的构造函数很多,我们挑一个参数最多的。其他构造器也差不多,无非就少几个参数,缺少的参数一般会选择默认的值。
public ThreadPoolExecutor( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
三、线程池的主要参数
依据上述构造器,可以看出构造器中有几个主要参数:
(1)int corePoolSize:该线程池中核心线程数最大值
当线程池创建新线程时,如果当前线程总数小于corePoolSize,则创建的是核心线程,如果超过corePoolSize,则创建的是非核心线程。
在默认情况下,核心线程即使闲置也不会被销毁,会一致存活在线程池中。当然也可以让核心线程闲置一段时间后就被销毁,只需要设置allowCoreThreadTimeOut = true;
(2)int maximumPoolSize:该线程池中线程总数最大值
线程总数 = 核心线程数 + 非核心线程数。非核心线程:不是核心线程的线程。
(3)long keepAliveTime:该线程池中非核心线程闲置超时时长
一个非核心线程,如果处于闲置状态的时长超过这个参数所设定的时长,就会被销毁。
如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。
(4)TimeUnit unit:keepAliveTime的单位
TimeUnit是一个枚举类型,其包括:
1)NANOSECONDS:1微毫秒 = 1微秒/1000
2)MICROSECONDS:1微秒 = 1毫秒/1000
3)MILLISECONDS:1毫秒 = 1秒/1000
4)SECONDS:秒
5)MINUTES:分
6)HOURS:小时
7)DAYS:天
(5)BlockingQueue<Runnable> workQueue:该线程池中的任务队列,维护着等待执行的Runnable对象
当所有线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则创建非核心线程执行任务。
常见的workQueue类型:
1)SynchronousQueue:(这个消息队列相当于不存在一样) 这个队列结构接到任务时,会直接提交给线程处理,不会保留它,当没有空闲线程处理则创建一个线程去处理
2)LinkedBlockingQueue:(这个线程只给核心线程处理)这个队列接收到任务时,会检查当前线程数是否小于核心线程数,如果小于核心线程数,则创建核心线程来处理任务;如果大于核心线程数,则进入等待队列中。
3)ArrayBlockingQueue:(标注消息队列) 可以限定队列长度,接收任务的时候,如果没有达到corePoolSize的值,则新建核心线程去执行任务。如果达到了,则入队列等待。如果队列已满,则创建非核心线程去执行任务。如果线程总数超过maximumPoolSize,并且队列也满了,则发生错误。
4)DelayQueue:(会给线程延迟的消息队列) 传进去的任务必须先实现Delay接口,这个队列接收到任务时,首先先入队,只有达到指定的延迟时间,才会执行任务。
(6)ThreadFactory threadFactory:创建线程的方式
这是一个接口,你new的时候需要实现他的Thread newThread(Runnable)方法
例子:AsyncTask新建线程池的threadFactory参数源码
new ThreadFactory(){
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r){
return new Thread(r,"AsyncTask #"+mCount.getAndIncrement());
}
}
(7)RejectedExecutionHandler handler:线程池的拒绝策略,跑出异常专用的
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有一下四种拒绝策略:
1)AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
2)DiscardPolicy:丢弃任务,但是不抛出异常。
3)DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
4)CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。
使用场景分析:
1)AbortPolicy:如果是比较关键的业务,推荐使用此拒绝策略,这样在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
2)DiscardPolicy:使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。(如,网站阅读量统计)
3)DiscardOldestPolicy:此拒绝策略,是一种喜新厌旧的决绝策略,是否要采用这种拒绝策略。还得根据实际业务是否允许丢弃老任务来认真衡量。
4)CallerRunsPolicy:此策略不会舍弃任何任务,但会造成卡顿等问题。
四、线程池的策略
暂时未写
五、常见的四种线程池
- 这四种都继承ThreadPoolExecutor类,只不过传入的参数不一样而已
(1)CachedThreadPool(可缓存线程池)
只有非核心线程,最大线程数很大,每新来一个任务,当没有空闲线程的时候就会创建一个线程。同时还有超时机制,当空闲线程超过60s没有用的话,就会被回收。
他可以一定程度减少频繁创建/销毁线程,减少系统开销,适用于执行时间短且数量多的任务场景。
特点:
1)线程数量无限制
2)有空闲线程则复用空闲线程,若无空闲线程则新建线程
3)一定程度减少频繁创建/销毁线程,减少系统开销
(2)FixedThreadPool(定长线程池)
一个有指定的线程数的线程池,有核心的线程,里面有固定的线程数量,相应速度快。固定线程数量有系统资源设置,核心线程没有超时机制的,队列大小没有限制,除非线程池关闭了核心线程才会被回收。
特点:
1)可控制线程最大并发数(同时执行的线程数)
2)超出的线程会在队列中等待
(3)ScheduledThreadPool(周期线程池)
创建一个定长线程池,支持定时及周期性任务执行,通过schedule方法可以设置任务的周期执行。
特点:
1)支持定时及周期性任务执行
(4)SingleThreadExecutor(单线程化的线程池)
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO、LIFO、优先级)执行,每次任务到来后都会进入阻塞队列,然后按照顺序执行。
特点:
1)有且仅有一个工作线程执行任务。
2)所有任务按照指定顺序执行,即遵循队列的入队出队规则。
六、线程池的生命周期
生命周期状态切换如下图所示:

对五种状态来做说明:
| 状态 | 运行状态描述 |
| RUNNING | 接受新任务和处理排队任务 |
| SHUTDOWN | 不接受新任务,但处理排队任务 |
| STOP | 不接受任务,不处理排队任务,中断正在进行的任务 |
| TIDYING | 所有任务都已终止,workerCount为零 |
| TERMINATED | 转换到TIDYING状态的线程将运行terminated()钩子方法 |
七、线程池如何知道一个线程的任务已经执行完成
7-1)使用线程池的isTerminated ()方法
threadPool.isTerminated() 常用来判断线程池是否结束,结束了为TRUE。
使用threadPool.isTerminated() 方法,必须在shutdown()方法关闭线程池之后才能使用,否则isTerminated()永不为TRUE,线程将一直阻塞在该判断的地方,导致程序最终崩溃。
7-2)使用submit()方法:
在线程池中,有一个submit()方法,它提供了一个Future的返回值,我们通过Future.get()方法来获得任务的执行结果。
当线程池中的任务没执行完之前,future.get()方法会一直阻塞,直到任务执行结束。因此,只要future.get()方法正常返回,也就意味着传入到线程池中的任务已经执行完成了!
7-3)使用 CountDownLatch
CountDownLatch 是一个栅栏类似计数器,我们创建了一个包含 N 个任务的计数器,每个任务执行完计数器 -1,直到计数器减为 0 时,说明所有的任务都执行完了
更多java基础总结(适合于java基础学习、java面试常规题):
总结篇(9)---字符串及基本类 (1)字符串及基本类之基本数据类型
总结篇(10)---字符串及基本类 (2)字符串及基本类之java中公共方法及操作
总结篇(12)---字符串及基本类 (4)Integer对象
总结篇(14)---JVM(java虚拟机) (1)JVM虚拟机概括
总结篇(15)---JVM(java虚拟机) (2)类加载器
总结篇(16)---JVM(java虚拟机) (3)运行时数据区
总结篇(17)---JVM(java虚拟机) (4)垃圾回收
总结篇(18)---JVM(java虚拟机) (5)垃圾回收算法
总结篇(19)---JVM(java虚拟机) (6)JVM调优
总结篇(24)---Java线程及其相关(2)多线程及其问题
总结篇(25)---Java线程及其相关(3)线程池及其问题
总结篇(26)---Java线程及其相关(4)ThreadLocal
总结篇(27)---Java并发及锁(1)Synchronized
总结篇(31)---JUC工具类(1)CountDownLatch
总结篇(32)---JUC工具类(2)CyclicBarrier
本文详细介绍了线程池的概念、构造函数、主要参数及其策略,并对比分析了四种常见线程池的特点,帮助读者理解如何合理选择和配置线程池。
170万+





