王有志,一个分享硬核 Java 技术的互金摸鱼侠
加入 Java 人的提桶跑路群:共同富裕的Java人
今天是《面霸的自我修养》第 6 篇文章,我们一起来看看面试中会问到哪些关于线程池的问题吧。
数据来源:
- 大部分来自于各机构(Java 之父,Java 继父,某灵,某泡,某客)以及各博主整理文档;
- 小部分来自于我以及身边朋友的实际经历,题目上会做出标识,并注明面试公司。
叠“BUFF”:
- 八股文通常出现在面试的第一二轮,是“敲门砖”,但仅仅掌握八股文并不能帮助你拿下 Offer;
- 由于本人水平有限,文中难免出现错误,还请大家以批评指正为主,尽量不要喷~~
线程池是什么?为什么要使用线程池?
难易程度:🔥🔥
重要程度:🔥🔥🔥🔥🔥
面试公司:无
计算机中,线程的创建和销毁开销较大,频繁的创建和销毁线程会影响程序性能。利用基于池化思想的线程池来统一管理和分配线程,复用已创建的线程,避免频繁创建和销毁线程带来的资源消耗,提高系统资源的利用率。
线程池具有以下 3 点优势:
- 降低资源消耗,重复利用已经创建的线程,避免线程创建与销毁带来的资源消耗;
- 提高响应速度,接收任务时,可以通过线程池直接获取线程,避免了创建线程带来的时间消耗;
- 便于管理线程,统一管理和分配线程,避免无限制创建线程,另外可以引入线程监控机制。
Java 中如何创建线程池?
难易程度:🔥🔥
重要程度:🔥🔥🔥🔥
面试公司:无
Java 中可以通过 ThreadPoolExecutor 和 Executors 创建线程池。
使用 ThreadPoolExecutor 创建线程池
使用 ThreadPoolExecutor 可以创建自定义线程池,例如:
ExecutorService threadPoolExecutor = new ThreadPoolExecutor(
10,
20,
10,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
new ThreadPoolExecutor.AbortPolicy()
);
了解以上代码的含义前,我们先来看 ThreadPoolExecutor 提供的的构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
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;
}
ThreadPoolExecutor 提供了 4 个构造方法,但最后都会指向含有 7 个参数的构造方法上。我们一一说明这些参数的含义:
int corePoolSize
,线程池的核心线程数量,核心线程在线程池的生命周期中不会被销毁;int maximumPoolSize
,线程池的最大线程数量,超出核心线程数量的非核心线程;long keepAliveTime
,线程存活时间,非核心线程空闲时存活的最大时间;TimeUnit unit
,keepAliveTime 的时间单位;BlockingQueue<Runnable> workQueue
,线程池的任务队列;ThreadFactory threadFactory
,线程工厂,用于创建线程,可以自定义线程;RejectedExecutionHandler handler
,拒绝策略,当任务数量超出线程池的容量(超过 maximumPoolSize 并且 workQueue 已满)时的处理策略。
使用 Executors 创建线程池
除了使用 ThreadPoolExecutor 创建线程池外,还可以通过 Executors 创建 Java 内置的线程池,Java 中提供了 6 种内置线程池:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
ExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
ExecutorService workStealingPool = Executors.newWorkStealingPool();
Tips:关于这 6 种线程池的详细解释,参考下一题。
Java 中提供了哪些线程池?
难易程度:🔥🔥
重要程度:🔥🔥🔥🔥
面试公司:无
Java 中提供了 6 种线程池,可以通过 Executors 获取。
FixedThreadPool
通过 Executors 创建 FixedThreadPool 的代码如下:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
FixedThreadPool 是固定线程数量的线程池,通过Executors#newFixedThreadPool
方法获得,源码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
本质上是通过 ThreadPoolExecutor 创建的线程池,核心线程数和最大线程数相同,工作队列使用 LinkedBlockingQueue,该队列最大容量是 Integer.MAX_VALUE
。
SingleThreadExecutor
通过 Executors 创建 SingleThreadExecutor 的代码如下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
SingleThreadExecutor 是只有一个线程的线程池,通过Executors#newSingleThreadExecutor
方法获得,其源码如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
依旧是通过 ThreadPoolExecutor 创建的线程池,最大线程数和核心线程数设置为 1,工作队列使用 LinkedBlockingQueue。SingleThreadExecutor 适合按顺序执行的场景。
ScheduledThreadPool
通过 Executors 创建 ScheduledThreadPool 的代码如下:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
ScheduledThreadPool 是具有定时调度和延迟调度能力的线程池,通过Executors#newScheduledThreadPool
方法获得,其源码如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
与前两个不同的是 ScheduledThreadPool 是用过 ScheduledThreadPoolExecutor 创建的,源码如下:
public class Executors {
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
}
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue());
}
}
追根溯源的话 ScheduledThreadPoolExecutor 依旧是通过 ThreadPoolExecutor 的构造方法创建线程池的,能够实现定时调度的特性是因为ScheduledThreadPoolExecutor#execute
方法和ScheduledThreadPoolExecutor#schedule
方法实现的:
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<Void> t = decorateTask(command, new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit), sequencer.getAndIncrement()));
delayedExecute(t);
return t;
}
}
因为 ScheduledThreadPoolExecutor 并不是线程池中的重点内容,这里我们不过多讨论源码的实现,我们接下来看 ScheduledThreadPoolExecutor 该如何使用:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println("当前时间:" + simpleDateFormat.format(new Date()));
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行时间:" + simpleDateFormat.format(new Date()) + ",延迟3秒执行");
}
}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("执行时间:" + simpleDateFormat.format(new Date()) + ",每3秒执行一次");
}
}, 0, 3, TimeUnit.SECONDS);
SingleThreadScheduledExecutor
通过 Executors 创建 ScheduledExecutorService 的代码如下:
ExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
与 ScheduledThreadPool 相同 SingleThreadScheduledExecutor 也是具有定时调度和延迟调度能力的线程池,同样的 SingleThreadScheduledExecutor 也是通过 ScheduledThreadPoolExecutor 创建的,不同之处在于 ScheduledThreadPool 并不限制核心线程的数量,而 SingleThreadScheduledExecutor 只会创建一个核心线程。
CachedThreadPool
通过 Executors 创建 CachedThreadPool 的代码如下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
CachedThreadPool 是可缓存线程的线程池,通过Executors#newSingleThreadExecutor
方法获得,其源码如下:
public static ExecutorService newCachedThreadPool()