Java线程池八股文:七大参数、执行原理与避坑指南
2026/6/10 3:48:28 网站建设 项目流程

线程池的由来:

回顾服务器并发处理的发展史,其实就是一部不断“降本增效”的进化史。最开始,我们习惯为每个客户端请求单独创建一个进程,但这种“一人一岗”的模式开销实在太大。后来,更轻量的线程取代了进程,大大缓解了资源压力。不过,当系统对性能的要求达到极致时,哪怕是线程的创建与销毁也成了瓶颈。于是,线程池被推上了历史舞台,它通过提前创建并复用线程,彻底解决了频繁创建销毁的开销问题,让高并发处理变得更加从容。

线程池的优点:

·线程复用:在使用线程池时,ThreadPoolExecutor(线程池执行器)把线程提前创建好(默认情况下为懒汉模式,不会立马创建线程,等到我们向线程池提交(submit)第一个任务后才开始创建,当然也可以强制提前创建,这里我们不过多讨论),放到一个“池”中,需要用的时候,随时去取,用完了就还回到池子中

·方便管理:我们在创建线程池时,可以传入七大参数来定制我们需要的那一款线程池

为什么我们认为,直接创建线程开销比从池中取线程开销更大呢?

这里我们就要提到操作系统的用户态内核态

一个操作系统 = 内核 + 配套的应用程序

内核包含操作系统的各种核心功能:

(1)管理硬件设备

(2)给软件提供稳定的运行环境

我们认为:

从线程池中取现成的线程,纯应用程序代码就可以完成【可控】

从操作系统创建新线程,就需要操作系统内核配合完成【不可控】

我们通常认为,可控的过程要比不可控的过程更高效,所以说如果有一段代码,需要进入到内核中,由内核负责完成一系列工作,这个过程是不可控的,我们写的代码干预不了,效率也就更低,而使用线程池,就可以省下应用程序切换到内核中运行这样的开销

为什么说操作系统创建线程是不可控的?

假设有这样一个场景(操作系统就是柜员,身份证是我们要创建的线程)

假设我们要去办理业务,但是忘记带身份证了

(1)我们可以选择自己去旁边的自主复印机复印一张出来,全程都是自己操作的,可见的【可控】

(2)我们选择让柜员帮我们复印一张,那么这个柜员就会去后台帮我们复印,但是谁知道他会不会中途去上个厕所,或者接个电话,抽个烟后再帮我们复印呢?整个过程不可见,我们只能在大堂盯着窗口等着这个柜员【不可控】

所以说,操作系统通过内核负责完成的一系列工作(包括创建线程)是不可控的,无法干预的,效率低的

而使用线程池,就可以将不可控变为可控,省下应用程序切换到内核中这样的开销

如何创建线程池?

在实战中,强烈建议不要使用Executors工厂类来创建线程池,而是直接使用ThreadPoolExecutor类进行手动配置8。这是因为Executors内部默认使用的无界队列或无上限的最大线程数,极易在任务激增时引发内存溢出(OOM)或系统崩溃

代码实例:

ThreadPoolExecutor executor = new ThreadPoolExecutor(

corePoolSize,

maximumPoolSize,

keepAliveTime,

unit,

workQueue,

threadFactory,

handler

);

认识七大参数

这里我们将一个 线程池 比作一家公司

一、corePoolSize(核心线程数)

核心线程数就好比一家公司的正式员工,负责处理公司的各种业务,即使由于公司没有业务导致长时间处于空闲状态,也不会被踢(销毁)

二、maximumPoolSize(最大线程数)

最大线程数又包括核心线程数和非核心线程数,也就是公司允许存在的最大员工数量,我们将非核心线程比作实习生

三、keepAliveTime(允许实习生最大摸鱼时间)

如果公司长期没有业务,处于空闲时期,公司不会马上踢掉这些实习生,如果实习生在公司指定最大摸鱼时间后还是没有活干,公司就会踢掉这些实习生(非核心线程)

四、unit(摸鱼时间单位)

一个枚举类型,里面有指定摸鱼时间的时间的各种单位,比如秒,分钟,天...

五、workQueue(工作队列,公司处理业务的地方)

公司的大厅(处理业务的地方),员工们(线程们)会不断从中拿任务并执行

六、ThreadFactory(工厂模式,公司的人力资源部)

工厂模式也属于一种设计模式,地位关系与我们之前提到的单例模式这一设计模式是并列的,

公司通过人力资源部,统一高效创建不同部门和各种员工(发工牌,取名....),也方便以后排查谁在干活。

工厂模式分为很多种,在业务中我们根据实际情况自己实现并传参即可

这里我们写一个简单工厂模式:

我们假设一个点有两种表示方式,一个是普通的 xy 坐标表达式,一个是通过 半径r 和 角度a 的坐标表达式,但我们在创建Point这个类,我们发现构造方式只有一个,也就是只能通过一种方式来表达这个点,这里就可以引出我们的工厂设计模式(通过不同的静态方法实现不同的表达式创建对象)

class Point{ private double x; private double y; private Point(double x,double y){ this.x = x; this.y = y; } public static Point makePointByXY(double x,double y){ Point point = new Point(x,y); return point; } public static Point makePointByRA(double r,double a){ double x = r*Math.cos(a); double y = r* Math.sin(a); Point point = new Point(x,y); return point; } }

这样我们就避免了构造方法单一的创建对象方式,转而使用静态方法实现多种创建对象方法

七、handle(拒绝策略,公司里的保安)

  • 对应关系:大厅里的保安,或客满时的应对方案。
  • 业务逻辑:当所有的正式员工都在忙,等候区也挤满了人,且实习生也招满了(达到了最大线程数),这时候如果还有新业务进来,保安就必须出面处理了。常见的保安策略有:
    • AbortPolicy(直接赶走):拒绝接单,并大喊“客满啦,不接了!”(抛出异常)5。
    • CallerRunsPolicy(老板自己干):让提交这个业务的人自己去处理,起到限流的作用(由调用线程自己执行)。
    • DiscardPolicy(默默无视):保安直接把新客户请走,不作任何通知(静默丢弃任务)。
    • DiscardOldestPolicy(把排队最久的赶走):保安把等候区排队时间最长的人请出去,让新客户补上(丢弃最老的任务)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询