Java线程池详解:面试官最爱问的并发编程问题

2026-03-30
1
-
- 分钟
|

🤔 面试官问:“你能说说在 Java 里创建多线程有哪几种方式吗?在实际的项目开发中,我们通常会用哪种方式来管理线程?”

📚 基础的三种方式(面试教科书答案)

  1. 继承 Thread 最简单粗暴的方法。自己写一个类继承 Thread,把要干的活写在 run() 方法里,然后 start() 启动。
  • 缺点:Java 是单继承的。如果你继承了 Thread,就不能再继承其他类了,在项目里显得极其死板,所以极少使用。
  1. 实现 Runnable 接口 最常用的基础方法。写一个类实现 Runnable 接口。因为是实现接口,你的类还可以自由继承别的类,扩展性很好。

  2. 实现 Callable 接口 前面两种方法都有一个致命弱点:干完活没有返回值(比如你想让线程帮你算个账,它算完没法直接把结果返回给你),而且不能抛出异常。如果你需要返回值,就必须实现 Callable 接口。


💼 实际项目中的终极方案:线程池(Thread Pool)

如果面试官问:“实际项目中怎么管理线程?” 千万不要说手动 new Thread() 正确答案是:必须使用线程池(Thread Pool)统一管理!

为什么要用线程池?我们可以用一个”开公司”的通俗例子来理解:

  • 手动 new Thread() 的灾难:假设你开了一家外包公司,每接一个小项目,你就跑到大马路上临时招一个员工(new Thread()),等他把项目做完,你立刻把他开除(线程销毁)。 招人和走离职流程极其耗费时间和系统资源。更可怕的是,如果双十一突然来了 1 万个并发请求,系统瞬间去 new 一万个线程,服务器的内存会瞬间被挤爆(OOM),直接死机。

  • 使用线程池的优势:聪明的做法是,公司长期养着一个固定人数的”核心开发团队”(线程池里的核心线程)。 有了新项目,直接丢给团队里的闲人去做,做完不用开除,接着做下一个项目。不仅响应速度极快(省去了创建和销毁的时间),而且能控制最大并发数,保证服务器安全稳定运行。

🏆 面试完美话术总结:

“在 Java 中创建线程基础的有三种方式:继承 Thread 类、实现 Runnable 接口和实现 Callable 接口。 但是在实际项目开发中,我们绝对不允许显式地手动去 new Thread(),而是会统一使用线程池(ThreadPoolExecutor)来管理线程。因为手动创建线程会带来极大的创建和销毁开销,并且在面临高并发时无法控制线程数量,容易导致内存溢出。使用线程池可以复用已创建的线程,提高响应速度,并有效控制系统的最大并发量。”

🤔 面试官眼睛一亮问:“既然你提到了 ThreadPoolExecutor,你能说说它里面有哪几个极其重要的’核心参数’吗?假如任务太多了,线程池处理不过来,它会怎么办(拒绝策略)?”

为了把这个讲透,我们必须亮出 ThreadPoolExecutor 最核心的 4 个参数(面试必背):

  1. corePoolSize(核心线程数):公司的”正式员工”编制。

  2. workQueue(工作队列/阻塞队列):公司的”大厅排队等候区”。

  3. maximumPoolSize(最大线程数):公司能容纳的”正式员工 + 临时工”的最大总人数。

  4. handler(拒绝策略):大厅挤爆了,临时工也招满了,对新来的客户宣读的”拒客令”。

当一个新任务(新客户)来到系统时,线程池是这样处理的(一定要记住这个顺序):

  • 第一步(正式员工接客):如果当前工作的人数 < corePoolSize,直接马上创建一个新核心线程来干活。

  • 第二步(去大厅排队 - 极其关键):如果正式员工都在忙,此时绝对不会立刻扩容招人! 而是让这个新任务去 workQueue(队列)里排队待命。

  • 第三步(终于触发扩容了!):如果新任务源源不断,导致整个队列都塞满了!这个时候,为了防止系统崩溃,线程池终于开始”扩容”了。它会创建非核心线程(临时工)来火速处理新任务,直到总线程数达到 maximumPoolSize

  • 第四步(大门紧闭,拒绝接客):如果队列满了,且总人数也达到了 maximumPoolSize(临时工也招不下了),这时候再来新任务,就会触发拒绝策略(handler)

🚫 面试官必问:那拒绝策略有哪些呢?

Java 官方默认提供了 4 种拒绝策略,你只要能用大白话说出来就行:

  1. AbortPolicy(默认):直接抛出 RejectedExecutionException 异常。就像保安直接把你轰出去,还大骂你一顿。

  2. CallerRunsPolicy:把任务退回给提交任务的那个线程去执行。就像老板把活儿甩给你,保安说:“我们不接,你自己去干吧!”(这个策略可以有效降低提交任务的速度,很常用)。

  3. DiscardPolicy:默默丢弃掉这个新任务,什么都不做,也不报错。就像保安当作没看见你。

  4. DiscardOldestPolicy:把队列里排在最前面(排得最久、最可怜)的那个任务踢掉,把新任务塞进去。就像保安把排在第一位的大冤种赶走,让你插队。

🤔 既然”临时工(非核心线程)“干完活了,公司总不能一直白养着他们吧?你知道线程池是怎么处理这些闲下来的临时工的吗?

当系统的高峰期过去,任务量降下来之后,“临时工”(非核心线程)在干完手头的活儿后,会去任务队列里拿下一个任务。如果队列已经空了(就像你说的没有饱满了,甚至没活儿了),它们就会进入”等待状态”。 如果在 keepAliveTime 设定的时间(比如 60 秒)内,它们依然没有等到新的任务,线程池就会认为”现在确实不需要这么多临时工了”,于是就会自动销毁这些非核心线程,把服务器的内存资源释放出来。 而那些”正式员工”(核心线程),默认情况下是永远不会被销毁的,他们会一直死等下一个任务(除非你特意开启了 allowCoreThreadTimeOut 参数)。

评论交流

文章目录