问题1:为什么要使用线程池?
传统的创建线程方式 new Thread(new Runnable(){}).start();
殊不知这种创建线程有以下缺点:
- 如果频繁创建和销毁线程会带来性能损耗,线程的创建和销毁都需要时间,如果创建和销毁的时间大于任务执行时间,反而得不偿失。
- 线程需要占用内存空间,大量的线程会抢占宝贵的内存资源,会导致oom,大量的线程也会给gc很大压力,延长gc的停顿时间。
- 大量的线程会抢占cpu资源,cpu在不停的做上下文切换,反而没有时间去处理线程运行的时候该处理的任务。
问题2:线程池有哪些好处?
- 降低资源消耗:通过池化技术进行线程复用,降低线程频繁创建和销毁带来的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
问题3:了解JDK Executors线程池吗?知道JDK提供了哪些默认的实现吗?
SingleThreadPool、FixedThreadPool、CachedThreadPool、ScheduledThreadPool
问题4:看过阿里巴巴java开发手册吗?知道为啥不允许使用默认的实现吗?
建议通过new ThreadPoolExecutor()
方式手动创建线程池,使我们更加明确线程池的运行规则,规避资源耗尽的风险。同时JDK默认实现的线程池实现有以下缺点:
- SingleThreadPool 和 FixedThreadPool 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,导致OOM
- CachedThreadPool 和 ScheduledThreadPool 允许创建的线程数量为Integer.MAX_VALUE 可能会创建大量线程,导致OOM
问题5:自定义线程池的几个常用参数?
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:空闲线程存活时间
- unit:时间单位
- workQueue:存放任务的缓冲队列
- threadFactory:创建线程的工厂
- handler:拒绝策略
问题6:自定义线程池的常用参数值如何得来?
根据任务类型,IO密集型还是计算密集型
问题7:线程池的执行流程是怎样的?
所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其执行过程如下:
- 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >=corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
- 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
其执行流程如下图所示:
问题8:线程池的复用机制是怎样的?
工作线程通过getTask
不断的获取任务,如果获取到任务,那么直接执行当前任务,否则阻塞等待任务的到来,如果超过一定的时间(空闲时间),还没有获取到任务,则线程被销毁。
问题9:了解线程池的状态吗?
ThreadPoolExecutor的运行状态有5种,分别为:
其生命周期转换如下入所示:
问题10:线程池的阻塞队列有哪些?
线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
问题11:线程池的拒绝策略有哪些?
任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。
拒绝策略是一个接口,其设计如下:
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
用户可以通过实现这个接口去定制拒绝策略,也可以选择JDK提供的四种已有拒绝策略,其特点如下:
问题12:线程池的参数可以动态调整吗?
public void setCorePoolSize(int corePoolSize)
public void setMaximumPoolSize(int maximumPoolSize)
public void setKeepAliveTime(long time, TimeUnit unit)
问题13:线程池被创建后里面有线程吗?如果没有的话,你知道有什么方法对线程池进行预热吗?
public boolean prestartCoreThread()
预热一个核心线程public int prestartAllCoreThreads()
预热所有的核心线程
问题14:核心线程数会被回收吗?需要什么设置?
public void allowCoreThreadTimeOut(boolean value)
问题15:一个线程池中的线程抛出了未经捕获的运行时异常,那么线程池会怎么处理这个线程?
- 当执行方式是execute时,可以看到堆栈异常的输出
- 当执行方式是submit时,堆栈异常没有输出(内部捕获了)。但是调用Future.get()方法时,可以捕获到异常
- 不会影响线程池里面其他线程的正常执行
- 线程池会把这个线程移除掉,并创建一个新的线程放到线程池中
- 线程池饱和之后跑出拒绝异常RejectException怎么办?一样的可以看到堆栈异常的输出
线程发生未捕获的异常后,最终都会走到Thread#dispatchUncaughtException
方法
判断当前线程是否设置了UncaughtExceptionHandler
,设置了会回调它的uncaughtException
方法,否则回调线程组的uncaughtException
方法
// ThreadGroup.java
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
// 这里判断是否设置了DefaultUncaughtExceptionHandler
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
// 设置了,则回调uncaughtException()方法
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
// 没有设置,则打印日志输出
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
问题16:线程池的关闭,shutdown和shutdownNow区别?
- shutdown:不再接受新任务,线程池中的任务会继续处理完
- shutdownNow:不再接受新任务,会中断线程池中的正在执行任务的线程
问题17:线程池的扩展和监控有哪些?
线程池对开发者提供了几组方法,我们可以监控到任务的执行状态。这些方法在 ThreadPoolExecutor.java
中
- beforeExecute
// 线程池中每个任务执行前,会调用此方法
protected void beforeExecute(Thread t, Runnable r) { }
- afterExecute
// 线程池中每个任务执行前,会调用此方法
protected void afterExecute(Runnable r, Throwable t) { }
- terminated
// 线程池终止了
protected void terminated() { }
- 可暂停执行的线程池
// 引用ThreadPoolExecutor.java提供的例子
public class PausableThreadPoolExecutor extends ThreadPoolExecutor {
private boolean isPaused;
private ReentrantLock pauseLock = new ReentrantLock();
private Condition unpaused = pauseLock.newCondition();
public PausableThreadPoolExecutor(...) {
super(...);
}
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try {
while (isPaused) unpaused.await();
} catch (InterruptedException ie) {
t.interrupt();
} finally {
pauseLock.unlock();
}
}
public void pause() {
pauseLock.lock();
try {
isPaused = true;
} finally {
pauseLock.unlock();
}
}
public void resume() {
pauseLock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
}
}}