很乐意为你深入浅出地剖析Java四大线程池的设计思想。我们将通过一个"包工头与雇工"的比喻,结合代码和时序图,彻底讲明白它们的原理。
核心思想:为什么需要线程池?
想象一个场景:
你是一个包工头(任务提交者 ),有很多零碎的任务(Runnable/Callable任务 )需要雇工(线程)来完成。比如搬砖、和水泥、砌墙。
- 糟糕的做法 :每次来一个新任务,你就现去劳务市场招聘一个工人(
new Thread()
),任务干完就把他辞退(线程销毁)。这非常浪费时间和金钱(系统资源 ),招聘和培训的成本(创建和销毁线程的开销)可能比任务本身还高。 - 聪明的做法 :你长期雇佣(线程复用 )一支稳定的工人队伍(线程池 )。有活时,派队伍里的工人去干;没活时,让工人们休息(阻塞等待)但不解散。这样效率极高,管理起来也方便。
Java的 ThreadPoolExecutor
就是帮你管理这支"队伍"的智能管理系统。
线程池的核心构造参数
在认识四大线程池前,必须先了解构建线程池的7个核心参数,它们决定了线程池的行为策略:
- corePoolSize(核心线程数) :长期雇佣的正式工人数,即使他们闲着也不开除。
- maximumPoolSize(最大线程数) :你的队伍最大规模,包括正式工和临时工。
- keepAliveTime(空闲线程存活时间) :临时工(超出核心线程数的线程)如果空闲这么久,就会被辞退以节省开支。
- unit(时间单位) :存活时间的单位。
- workQueue(工作队列) :一个任务队列。新任务来了,如果正式工都没空,就把任务先放在这个队列里排队。
- threadFactory(线程工厂) :用工标准,如何招聘和培训工人(如何创建新线程)。
- handler(拒绝策略) :当任务队列也满了,并且所有工人(线程)都在忙时,如何拒绝新来的任务。
四大线程池的本质,就是使用 Executors
工厂类,用不同的参数预先配置好了这个"智能管理系统"。
四大线程池详解
1. FixedThreadPool(固定大小线程池)
-
雇工故事 :你是一个小作坊老板,你只长期雇佣了3个固定工人(
corePoolSize = maximumPoolSize = 3
)。任务来了就派给空闲的工人。如果3个工人都在忙,新任务就在工作台(无界队列LinkedBlockingQueue
)上排队等着。因为你地方小,也养不起更多工人,所以从不招聘临时工。队列可以无限长,理论上不会触发拒绝策略。 -
设计思想:控制最大并发数,超出的任务排队执行。适用于负载较重的服务器,需要限制当前线程数量。
-
代码创建:
javaExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
-
源码参数:
javanew ThreadPoolExecutor( nThreads, // corePoolSize nThreads, // maximumPoolSize 0L, TimeUnit.MILLISECONDS, // keepAliveTime为0,但因为core==max,所以此参数无效 new LinkedBlockingQueue<Runnable>() // 无界队列 );
2. CachedThreadPool(可缓存线程池)
-
雇工故事 :你是一个劳务中介。一开始没有正式工(
corePoolSize = 0
)。有任务来了,就先看有没有空闲的临时工(maximumPoolSize = Integer.MAX_VALUE
),如果有就派活;如果没有就立刻招聘一个新的临时工(创建新线程 )。临时工如果1分钟(keepAliveTime = 60s
)没活干就被辞退。因为招聘速度极快(无核心线程,直接创建新线程 ),所以通常不需要任务排队(使用的队列是SynchronousQueue
,它不存储元素,只是做直接交接)。 -
设计思想:无限扩展,随需随建,空闲回收。适用于执行很多短期异步任务的小程序,或负载较轻的服务器。
-
代码创建:
javaExecutorService cachedThreadPool = Executors.newCachedThreadPool();
-
源码参数:
javanew ThreadPoolExecutor( 0, // corePoolSize Integer.MAX_VALUE, // maximumPoolSize,非常大(约21亿) 60L, TimeUnit.SECONDS, // keepAliveTime new SynchronousQueue<Runnable>() // 同步移交队列 );
3. SingleThreadExecutor(单线程线程池)
-
雇工故事 :你是一个老板,但只雇佣了1个工人(
corePoolSize = maximumPoolSize = 1
)。所有任务都必须由这个工人按顺序完成。任务来了,他如果空闲就立马干,如果正在忙,新任务就在他旁边的工作队列(无界队列LinkedBlockingQueue
)里排好队,等他干完一件再干下一件。保证所有任务按提交顺序执行。 -
设计思想:保证所有任务按顺序(FIFO, LIFO, 优先级)执行。不需要处理线程同步问题。
-
代码创建:
javaExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
-
源码参数:
javanew ThreadPoolExecutor( 1, // corePoolSize 1, // maximumPoolSize 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() // 无界队列 );
4. ScheduledThreadPool(定时任务线程池)
-
雇工故事 :你是一个物业经理,手下有一支专门负责定时任务的工人队伍(核心线程数
corePoolSize
由你指定)。他们的工作不是立即执行,而是:a) 在指定的延迟后执行一次(schedule
);b) 定期固定延迟重复执行(scheduleWithFixedDelay
);c) 定期固定频率执行(scheduleAtFixedRate
)。它使用了一个特殊的延迟队列(DelayedWorkQueue
)来安排任务执行的顺序和时间。 -
设计思想:专门用于在给定的延迟后运行任务,或者定期执行任务。
-
代码创建:
java// 指定核心线程数 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
-
核心用法:
java// 延迟一次执行 scheduledThreadPool.schedule(new RunnableTask(), 5, TimeUnit.SECONDS); // 固定频率执行(每次执行开始时间间隔固定) scheduledThreadPool.scheduleAtFixedRate(new RunnableTask(), 1, 3, TimeUnit.SECONDS); // 固定延迟执行(上次执行结束到下次执行开始间隔固定) scheduledThreadPool.scheduleWithFixedDelay(new RunnableTask(), 1, 2, TimeUnit.SECONDS);
调用过程时序图(以FixedThreadPool为例)
下图描绘了向一个 corePoolSize=2, maxPoolSize=3, queueSize=2
的线程池提交任务的完整流程,包括任务队列满时创建新线程(临时工),以及达到最大线程数后的拒绝策略。

流程文字解释:
- T1, T2 提交:核心线程数未满,直接创建核心Worker1和Worker2来执行。
- T3, T4 提交:核心线程已满,任务被放入工作队列排队。
- T5 提交 :核心线程忙,且队列已满(本例中队列大小为2),但当前总线程数(2)小于最大线程数(3),因此创建临时线程Worker3来执行T5。
- T6 提交 :核心线程忙 + 队列已满 + 线程数已达最大值 → 触发拒绝策略。
- Worker事件循环 :每个Worker线程在完成手头任务后,会不断地去队列里取新的任务来执行。核心线程 会一直等待(
take()
),而非核心线程 会在超时时间内(poll(timeout)
)还拿不到任务时就被回收。
Android中的注意事项与实践
虽然 Executors
很方便,但在Android(尤其是App)开发中,需要谨慎使用:
- FixedThreadPool 和 SingleThreadExecutor :因为它们使用无界队列
LinkedBlockingQueue
,如果任务积压过多,可能会导致内存溢出(OOM)。 - CachedThreadPool:因为它最大线程数几乎是无限的,如果任务数量非常多,可能会创建大量线程,耗尽系统资源,导致OOM或卡顿。
最佳实践:直接使用 ThreadPoolExecutor
自定义线程池。
根据你的业务场景(CPU密集型?IO密集型?),精心设置核心参数:
- CPU密集型(如图像处理):核心线程数 ≈ CPU核数。
- IO密集型(如网络请求):核心线程数可以设置得多一些,如 CPU核数 * 2。
- 使用有界队列 (如
ArrayBlockingQueue
)并指定合理的容量。 - 定义明确的拒绝策略(如丢弃、回退到调用线程执行等)。
java
// Android中推荐的自定义线程池示例
int corePoolSize = Runtime.getRuntime().availableProcessors();
int maxPoolSize = corePoolSize * 2;
long keepAliveTime = 60L;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(128);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
ExecutorService myExecutor = new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
workQueue,
threadFactory,
handler
);
希望这个从架构师视角,结合故事、代码和时序图的讲解,能让你对Java四大线程池有彻底的理解。