文章目录
1. 线程池介绍
- 释义
百度百科这样定义,“线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务”。 - 优点
- 避免频繁创建和销毁线程,节省资源开销
- 限制线程数量,避免过多的线程创建占用太多的内存
- 可以统一管理资源
2. 使用场景
- 任务数多,但占用资源少
多发生于消息推送、短信通知等。 - 任务数不多,但占用资源大
多发生于文件流、批处理或批量数据加工处理,如日志收集、图片流压缩等。 - 限制资源消耗
如:资源占用大,任务多,处理效率不高的场景,对任务限流来保证系统的可用性及稳定性。
3. 常见线程池特点和用法
3.1 线程池构造函数的参数
- corePoolSize
核心线程数,线程池在完成初始化后,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务 - maxPoolSize
线程池有可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一个上限,这就是最大量maxPoolSize - keepAliveTime
如果线程池当前的线程数多余corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会终止 - threadFactory
新的线程是由于ThreadFactory创建的,默认使用Executor.defaultThreadFactory(),创建出来的线程都在同一个线程组。拥有同样的NORM_PRIORITY优先级并不是守护线程,如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等。 - workQueue
有三种常见的队列类型- SynchronousQueue
直接交换,队列容量为0 - LinkedBlockingQueue
无界队列 - ArrayBlockingQueue
有界队列
- SynchronousQueue
3.2 线程池添加线程规则及特点
- 添加线程规则
- 如果线程数小于corePoolSize,即使其它工作线程处于空闲状态,也会创建一个新线程来运行新的任务
- 如果线程数等于(或大于)corePoolSize但少于maximumPoolSize,则将任务放入队列
- 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务
- 如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝该任务
- 是否需要增加线程的判断顺序
- corePoolSize
- workPoolSize
- maxPoolSize
- 举例
线程池:核心数大小为5,最大池大小为10,队列为100
解析:因为线程中的请求最多会创建5个,然后任务将被添加到队列中,直到达到100。当队列已满时,将创建最新的线程maxPoolSize,最多到10个线程,如果再来任务,就拒绝。
- 添加线程特点
- 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池
- 线程池希望保持较少的线程数,并且只有在负载变得很大的时候才增加它
- 通过设置maximumPoolSize为很高的值,如Integer.MAX_VLUE,可以允许线程池容纳任意数量的并发任务
- 只有在队列填满时才创建多于corePoolSize的线程,所以如果使用无界队列(LinkedBlockingQueue),那么线程数就不会超过corePoolSize
3.3 常见线程池介绍
- FixThreadPool
- 可重用固定线程数的线程池。只有核心线程,且数量固定,没有非核心线程。keepAliveTime默认为0L,即多余的线程会被立即终止。任务采用无界的阻塞队列LinkedBlockingQueue(容量默认为Integer.MAX_VALUE)。
//源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- singleThreadExecutor
- 单线程化线程池,相当于单线程执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。该线程池可以确保所有任务的执行书序都会根据任务的提交顺序进行。其核心线程数与最大线程数固定为1,采用无界阻塞队列LinkedBlockingQueue。
//源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- cachedThreadPool
- 可缓存线程池。当有个请求进入线程池内,线程池将会启用一个线程,当再次有个请求进入线程池内,并且上个线程未结束,仍然会启用一个线程,当有线程执行完毕后,这个线程不会被清除,而是被缓存,当有请求进入时, 直接使用缓存线程调用,当线程一直不被使用,缓存最多持续1分钟(AliveTime默认值),就会被线程池销毁。
- 使用的SynchronousQueue是一个不存储元素的BlockingQueue。每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
//源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- scheduledThreadPool
延迟线程池,支持定时及周期性任务执行。
//源码
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
4. 线程池状态
- running
接受新任务并处理排队任务 - shutdown
不接受新任务,但处理排队任务 - stop
不接受新任务,也不处理排队任务,并中断正在进行的任务 - tidying
所有任务都已停止,workerCout为零时。线程会转换到tidying状态,并将运行terminate()钩子方法。当线程池中所有任务已被终止, 这个ThreadPoolExecutor实例就会进入停止态。 - terminated
当线程池处于tidying,并调用terminated()方法,执行完毕之后,就会进入结束态,此状态也表示整个线程池生命周期的结束。
5. 任务太多如何拒绝
- 拒绝时机
- 当Executor关闭时,提交新任务会被拒绝
- 当Executor对最大线程和工作队列容量使用优先边界并且已经饱和时
- 4种拒绝策略
- AbortPolicy
默认拒绝策略,拒绝任务并抛出异常 - DiscardPolicy
直接拒绝任务,不抛出错误 - DiscardOldestPolicy
触发拒绝策略,只要还有任务新增,一直会丢弃阻塞队列的最老的任务,并将新的任务加入 - CallerRunsPolicy
使用调用线程直接运行任务
- AbortPolicy
6. 线程池钩子方法
可用于日志或统计,具体为重写beforeExecute,可参考文末
7. 如何停止线程池
- shutdown
执行之后,等已有任务完成后才终止。执行之后,新任务不增加 - isShutdown
isShutdown 用于判断状态 - isTerminated
用于判断是否终止 - awaitTermination
用于检测和判断,一段时间内是否所有线程执行完毕,返回的三种情况,返回之前是阻塞的- 所有线程是否执行完毕
- 等待时间到了
- 等待过程被中断
- shutdownNow
立刻关闭所有线程,包括正在执行的线程
8. 使用线程池的注意点
- 避免任务堆积
- 避免线程数过度增加
- 排查线程泄露
9. 技能追问
- 线程池应该手动创建还是自动创建?
- 手动创建更好,可以更加明确线程池创建的规则,避免资源耗尽的风险
- 自动创建线程池可能带来的风险
- newFixedThreadPool、newSingleThreadExecutor
由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候也就是请求堆积的时候,会容易占用大量内存,可能导致OOM - newCachedThreadPool、scheduledThreadPool
maximumPoolSize都被设置了Integer.MAX_VALUE,这可能会创建数量非常多的线程,甚至导致OOM
- newFixedThreadPool、newSingleThreadExecutor
- 线程池里的线程数量设定为多少?
- CPU密集型(加密、计算hash等):最佳线程数为CPU核心数的1-2倍左右
- 耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于CPU核心数很多倍,以JVM线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,参考Brain Goetz推荐的计算方法:线程数=CPU核心数*(1+平均等待时间/平均工作时间)
钩子参考代码
public class PauseableThreadPool extends ThreadPoolExecutor {
private final ReentrantLock lock = new ReentrantLock();
private Condition unpaused = lock.newCondition();
private boolean isPaused;
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory,
handler);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
lock.lock();
try {
while (isPaused) {
unpaused.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void pause() {
lock.lock();
try {
isPaused = true;
} finally {
lock.unlock();
}
}
public void resume() {
lock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10l,
TimeUnit.SECONDS, new LinkedBlockingQueue<>());
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("我被执行");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10000; i++) {
pauseableThreadPool.execute(runnable);
}
Thread.sleep(1500);
pauseableThreadPool.pause();
System.out.println("线程池被暂停了");
Thread.sleep(1500);
pauseableThreadPool.resume();
System.out.println("线程池被恢复了");
}
}