答完这道线程池场景题,面试官让我回家等通知

本文探讨了一道面试题,涉及线程池在处理任务时的机制。当有100个任务进入一个核心线程数为10,最大线程数为20,任务队列为100的线程池时,答案可能因线程池类型而异。正常情况下,会先启动10个核心线程,剩余任务进入队列。但在EagerThreadPoolExecutor中,会立即启动20个线程,剩余任务进入队列。理解线程池的工作原理对于避免面试陷阱至关重要。

引言

一道面试题开始~~面试官:"假设我们有一个线程池,核心线程数为10,最大线程数也为20,任务队列为100。现在来了100个任务,线程池里现在有几个线程运行?"粉丝豪:"应该是10吧。"面试官:"你确定?"粉丝豪:"确定啊,就是10…"于是乎,粉丝豪就回去等通知了~

此题的陷阱,大家如果看出来了,本文就不用看了!其实这道题正确的答案是"不一定!"因为并没指明是哪一种线程池机制,带着我们的疑问继续往下看!

ps:不要纠结这题是不是咬文嚼字,现在人多如米,题目就是这样坑~

正文

先进队列,到最大值,再起线程

这种情况是大家最容易想到的情况,因为JDK中的线程池,也就是ThreadPoolExecutor就是这种机制的!

OK,我们先来看一下ThreadPoolExecutor的execute方法,如下图所示


看到三个红框了吧(其实源码里有解释),对应的三步分别是

  • (1)判断当前活跃线程数是否小于corePoolSize,如果小于,则调用addWorker创建线程执行任务
  • (2)如果不小于corePoolSize,则将任务添加到workQueue队列
  • (3)如果放入workQueue失败,则创建线程执行任务,如果这时创建线程失败(当前线程数不小于maximumPoolSize时),就会调用reject(内部调用handler)拒绝接受任务。

用一张图来解释如下

如图所示,默认的机制为线程池里的核心线程数不够了,后面进来的任务会先丢队列,当队列满了,才起新线程。

因此,按照这套机制!粉丝豪的回答是正确的,当有100个任务添加进来时,剩下先起10个核心线程,剩下90个任务都丢进队列里,因此线程池里只有10个线程在执行!

但是,有一种线程池机制的是这样的

先起线程,到最大值,再进队列

不知道大家有没听过在dubbo中,有一种线程池叫EagerThreadPoolExecutor线程池。该线程池的execute方法如下所示


它调的还是父类的execute方法,也还是ThreadPoolExecutor中的execute方法!
但是,它的队列!是一种自定义队列,叫TaskQueue,它的offer方法如下

这个offer方法的最核心的是红框中那步,当前线程数小于最大线程数时,则直接返回false。

ThreadPoolExecutor中的execute方法中的第二步的条件中,如果workQueue.offer返回为fasle,则直接进入第三步,创建新任务!如下图所示

EagerThreadPoolExecutor线程池通过自定义队列的这么一种形式,改写了线程池的机制。这种线程池的机制是核心线程数不够了,先起线程,当线程达到最大值后,后面的任务就丢进队列!

因此,如果按照这么一套机制,粉丝豪的答案就不正确了。当100个任务添加进来时,直接会起20个线程,剩下80个任务都丢进队列!

发散题

这是一道真实的发散题,其实问法也是差不多的。如果上面的内容能看懂,这道题也能答的上来!

总结

小小的线程池,大大的坑~希望大家有所收获~~

本文卒!

### 线程池常见面试及解 #### 1. **线程池的作用是什么?** 线程池是一种管理线程的机制,它通过复用已创建的线程来执行多个任务,从而避免频繁创建和销毁线程所带来的性能开销。线程池可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。此外,线程池还提供定时执行、定期执行、单线程、并发数控制等功能 [^2]。 #### 2. **Java线程池的创建方式有哪些?** Java 提供了多种线程池的创建方式,主要通过 `Executors` 工厂类来创建不同类型的线程池: - **定长线程池**(`newFixedThreadPool`):线程池中的线程数量固定,适用于负载较重的服务器环境。 ```java ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); ``` - **缓存线程池**(`newCachedThreadPool`):线程池中的线程数量不固定,会根据任务数量动态调整,适用于执行大量短期异步任务的场景。 ```java ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); ``` - **单一线程池**(`newSingleThreadExecutor`):线程池中只有一个线程,适用于需要保证任务顺序执行的场景。 ```java ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); ``` - **定时线程池**(`newScheduledThreadPool`):支持定时任务的调度,适用于需要周期性执行任务的场景。 ```java ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3); ``` #### 3. **线程池核心参数有哪些?** 线程池核心参数包括: - **corePoolSize**:线程池中保持的最小线程数,即使线程处于空闲状态。 - **maximumPoolSize**:线程池中允许的最大线程数。 - **keepAliveTime**:当线程池中的线程数量超过 corePoolSize 时,空闲线程的存活时间。 - **unit**:存活时间的单位。 - **workQueue**:用于存放待执行任务的阻塞队列。 - **threadFactory**:用于创建新线程的工厂对象。 - **handler**:拒绝策略,当任务无法被提交线程池时所采取的策略 。 #### 4. **线程池的工作流程是怎样的?** 线程池的工作流程如下: 1. 当提交一个新任务时,如果当前线程数小于 corePoolSize,则创建一个新线程来执行任务。 2. 如果当前线程数等于或超过 corePoolSize,则将任务放入阻塞队列中等待执行。 3. 如果阻塞队列已满,并且当前线程数小于 maximumPoolSize,则创建新的线程来处理任务。 4. 如果阻塞队列已满,并且当前线程数等于或超过 maximumPoolSize,则根据拒绝策略处理任务 [^2]。 #### 5. **线程池的拒绝策略有哪些?** Java 提供了以下几种拒绝策略: - **AbortPolicy**:默认策略,抛出 `RejectedExecutionException` 异常。 - **CallerRunsPolicy**:由提交任务的线程来执行任务。 - **DiscardOldestPolicy**:丢弃队列中最老的任务,然后重新提交当前任务。 - **DiscardPolicy**:直接丢弃任务,不做任何处理 [^2]。 #### 6. **如何关闭线程池?** 关闭线程池有两种常用方法: - **`shutdown()`**:不会立即终止线程池,而是等待所有任务成后关闭线程池。 ```java executor.shutdown(); ``` - **`shutdownNow()`**:尝试立即终止线程池,返回未执行的任务列表。 ```java List<Runnable> tasks = executor.shutdownNow(); ``` 在关闭线程池之前,应确保所有任务都已成执行,否则可能导致任务丢失 。 #### 7. **线程池中任务执行异常如何处理?** 在线程池中,任务执行过程中抛出的异常不会直接传播到主线程,因此需要通过以下方式处理: - **使用 `try-catch` 捕获异常**:在任务内部捕获并处理所有异常。 ```java executor.execute(() -> { try { // 任务代码 } catch (Exception e) { // 处理异常 } }); ``` - **自定义 `ThreadFactory`**:设置线程的未捕获异常处理器。 ```java ThreadFactory factory = r -> { Thread t = new Thread(r); t.setUncaughtExceptionHandler((thread, ex) -> { // 处理未捕获的异常 }); return t; }; ExecutorService executor = Executors.newFixedThreadPool(3, factory); ``` - **使用 `Future` 获取异常信息**:如果使用 `submit()` 方法提交任务,可以通过 `Future.get()` 获取任务执行过程中抛出的异常。 ```java Future<?> future = executor.submit(() -> { // 任务代码 }); try { future.get(); } catch (ExecutionException e) { // 处理任务异常 } ``` #### 8. **线程池中如何避免资源泄漏?** 为了避免线程池中的资源泄漏,应确保: - 在应用程序结束时调用 `shutdown()` 或 `shutdownNow()` 关闭线程池。 - 避免长时间阻塞线程,防止线程无法释放。 - 使用 `ThreadLocal` 时,确保在线程结束时正确清理变量。 - 避免使用 `Thread.stop()` 或 `Thread.suspend()` 等已被废弃的方法 。 ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值