Java 线程池的核心接口是 Executor 和 ExecutorService,最常用的实现类是 ThreadPoolExecutor。
并且 Java 还帮我们搞了一个工厂类 Executors,用于创建特定需求的 ThreadPoolExecutor,省去了麻烦的构造传参过程。
它们的关系如下图所示:

为什么常用
ExecutorService而不用ThreadPoolExecutor直接接收线程池对象❓❓❓ ① 面向接口编程的思想
ExecutorService 是接口,ThreadPoolExecutor 是实现类。接口能屏蔽实现细节,提高代码的灵活性。比如以后你想换成另一个线程池实现,比如ScheduledThreadPoolExecutor,只用改一行初始化、不用大改其他代码。
② 常用的工厂方法都返回 ExecutorService
常见的线程池创建方法返回的本来就是 ExecutorService 接口:

这样别人用你的代码/方法,只要依赖 ExecutorService 的 API 就行,不用知道是不是ThreadPoolExecutor,还是别的啥池子。
③ 实际开发只用到 ExecutorService 的方法就够了
execute()、submit()、shutdown() 等等,全部定义在 ExecutorService 接口里。日常 99% 的场景都不需要依赖实现类的特有方法。activeCount,或者定制特殊的配置参数),才可能需要用到实现类 ThreadPoolExecutor 的专有方法。Executors 创建线程池public static void main(String[] args) {
// 1. 固定大小线程池:适用于负载稳定的场景
ExecutorService p1 = Executors.newFixedThreadPool(10);
// 2. 可缓存线程池:有任务就创建新线程,空闲线程会回收,适用于大量短生命周期的任务
ExecutorService p2 = Executors.newCachedThreadPool();
// 3. 单线程的线程池,保证任务顺序执行
ExecutorService p3 = Executors.newSingleThreadExecutor();
// 4. 定时/周期性线程池:适用于延迟或周期性任务调度
ExecutorService p5 = Executors.newScheduledThreadPool(10);
}线程池与阻塞队列的匹配关系如下表所示:
线程池类型 | 使用的阻塞队列 | 设计目标 |
|---|---|---|
FixedThreadPool | LinkedBlockingQueue | 固定线程数,任务队列无界,避免线程频繁创建 |
CachedThreadPool | SynchronousQueue | 线程数动态扩展,任务直接传递,适合短时任务 |
SingleThreadExecutor | LinkedBlockingQueue | 单线程顺序执行任务 |
ScheduledThreadPool | DelayedWorkQueue | 延迟或周期性任务调度 |
要手动创建线程池的话,可以看后面 ThreadPoolExecutor 参数的解释!
创建出线程池之后,就要提交 "任务" 到线程池中,由线程池自己调度或创建新线程来完成该 "任务"。下面是 Executor 中给出的两个提交任务的接口:
execute | submit(推荐⭐⭐⭐) | |
|---|---|---|
接收参数类型 | Runnable(无返回值) | Runnable | Callable |
返回值 | void(无返回) | Future(可获得返回和异常) |
任务异常处理 | 线程池会捕获但不会报告异常 | Future.get() 时抛出 ExecutionException |
任务中断支持 | 无 | 可以 cancel 中断任务 |
用途 | 简单任务,不关心结果 | 需要关心任务结果或异常 |
现代最佳实践其实推荐多用 submit,因为哪怕暂时不需要返回值,有 Future 也能后续扩展,比如取消、结果统计等。
// 1. 创建一个指定数量线程的线程池
ExecutorService p1 = Executors.newFixedThreadPool(10);
// 2. 让线程池中10个线程来处理100个打印任务
for(int i = 0; i < 100; ++i) {
int index = i;
p1.submit(() -> { // submit不处理返回值是没问题的!
System.out.println(Thread.currentThread().getName() + ":" + index);
});
}用 shutdown() 优雅关闭,不再接收新任务,等待现有任务执行完毕。
p1.shutdown();ThreadPoolExecutor 参数的理解💥💥💥ThreadPoolExecutor pool = new ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心闲置线程最大存活时间
TimeUnit unit, // keepAliveTime的时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
);corePoolSize:核心线程数量(不会被回收,相当于正式员工,一旦录用,永不辞退)maximumPoolSize:最大线程数量(相当于正式员工 + 临时工的数量,当临时工那部分闲置超过了 keepAliveTime,就会被炒掉)maximumPoolSize,新任务就会触发下面的 "拒绝策略"。keepAliveTime:非核心闲置线程最大存活时间(即临时工允许的闲置时间)allowCoreThreadTimeOut(true),那么核心线程如果也空闲时间超过该值也会被回收。unit:keepAliveTime的时间单位(纳秒、微妙、毫秒、秒、分钟、小时、天)workQueue:线程池内部存储待执行任务的阻塞队列threadFactory:创建线程的工厂类,控制创建线程的细节(如线程命名、优先级、是否为守护线程等)

Java 自带了现成的创建线程的工厂类,最常用的就是 Executors.defaultThreadFactory()handler:拒绝策略,就是任务太多,超过 workQueue 的容量后,要怎么处理,有四种内置选项:AbortPolicy(默认):直接抛出 RejectedExecutionException 异常DiscardPolicy:直接丢弃DiscardOldestPolicy:丢弃队列里最老的任务,然后再尝试入队当前任务CallerRunsPolicy:由提交任务的线程自己执行该任务ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, // corePoolSize
10, // maximumPoolSize
60, // keepAliveTime
TimeUnit.SECONDS, // 单位
new ArrayBlockingQueue<>(100), // 有界队列
Executors.defaultThreadFactory(), // 默认线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 如果要手动自定义线程工厂,可以按照下面这样子处理,然后传myFactory给ThreadPoolExecutor即可
AtomicInteger threadId = new AtomicInteger(1);
ThreadFactory myFactory = r -> {
Thread t = new Thread(r, "mythread-" + threadId.getAndIncrement());
t.setDaemon(true); // 设置线程为守护线程
return t;
};💥注意事项:
BlockingQueue<Runnable> 中的 Runnable 和 ThreadFactory.newThread(Runnable r) 中的 Runnable 是不同的,区别如下所示:ThreadFactory.newThread(Runnable r) 中的 rRunnable r 不是你的 "具体业务",而是线程池 "工作线程" 的调度骨架(Worker)。它的职责就是反复从任务队列 BlockingQueue<Runnable> 中取任务出来执行,一旦取到任务就执行任务的 run()。Runnable r 更像是 "消费者行为的实现",并不是你自己提交的具体业务任务。submit(Runnable r) 中的 rsubmit(Runnable r) 中的 r 和阻塞队列 BlockingQueue<Runnable> 中的 Runnable 是一回事,都是指具体业务,阻塞队列相当于提供了一个存放 submit 提交业务的空间!Worker 线程)来拿走处理。submit(Runnable r) 中的 r 是 "具体业务",这些业务会被当作 "原料" 投向线程池由工作线程拿去处理,而工作线程的属性等是由 ThreadFactory.newThread(Runnable r) 中的 r 提供的,并且这些工作线程由线程池自主调度,不需要程序员手动处理。任务提交
│
├─ 核心线程未满 → 创建核心线程执行
│
├─ 核心线程满 → 队列未满 → 放入队列等待
│
└─ 队列满 → 非核心线程未满 → 创建非核心线程执行
│
└─ 非核心线程满 → 拒绝策略allowCoreThreadTimeOut 可以让核心线程也根据空闲时间销毁实现一个简易的线程池,是为了帮助理解线程池中阻塞队列的作用、线程的执行这些环节,实现起来并不难,只需要用一个阻塞队列,然后启动线程执行阻塞队列中的任务即可!
public class MyThreadPool {
private BlockingQueue<Runnable> qe = new LinkedBlockingQueue<>();
public MyThreadPool(int n) {
for(int i = 0; i < n; ++i) {
Thread thread = new Thread(() -> {
// 让每个线程循环处理阻塞队列中的业务,然后执行
while (true) {
try {
Runnable r = qe.take();
r.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start(); // 别忘了启动线程
}
}
// 将具体业务插入到阻塞队列中,具体调度由阻塞队列自己处理
public void submit(Runnable r) {
try {
qe.put(r);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}测试代码非常简单,提交任务让线程池去处理即可,如下所示:
public static void main(String[] args) throws InterruptedException {
MyThreadPool mtp = new MyThreadPool(5);
for(int i = 0; i < 100; ++i) {
int index = i;
mtp.submit(() -> {
System.out.println(Thread.currentThread().getName() + "处理" + index + "号业务");
});
}
}
定时器是软件开发中的一个重要组件,就是程序中闹钟,时间到了就会执行某段提前设定好的代码。比如网络通信中的最大未响应时间,超过一段时间没有收到数据,此时程序应该尝试发起重新连接等等情况。
TimerTimer 是 Java 中提供的一种简单的定时任务调度工具类,用于安排一个任务在指定的时间执行一次,或者周期性地执行。
Timer 的接口如下表所示:
方法签名 | 说明 | 是否周期执行 | 延迟类型 |
|---|---|---|---|
schedule(TimerTask task, long delay) | 延迟 delay 毫秒后执行一次任务 | ❌ | 固定延迟 |
schedule(TimerTask task, Date time) | 在指定时间点 time 执行一次任务 | ❌ | 固定时间点 |
schedule(TimerTask task, long delay, long period) | 延迟 delay 毫秒后开始,每隔 period 毫秒执行一次任务 | ✅ | 固定延迟(上次任务结束后再延迟) |
schedule(TimerTask task, Date firstTime, long period) | 指定首次时间 firstTime,之后每隔 period 毫秒执行 | ✅ | 固定延迟 |
scheduleAtFixedRate(TimerTask task, long delay, long period) | 延迟 delay 毫秒后开始,每隔 period 毫秒强制执行一次(不管上次是否完成) | ✅ | 固定频率(时间点为准) |
scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 指定首次时间点 firstTime,之后按固定频率周期执行 | ✅ | 固定频率 |
cancel() | 取消当前定时器中所有已安排的任务 | — | — |
purge() | 清除已被取消的任务(返回清除的个数) | — | — |
💥注意事项:
在使用的时候,Timer 需要配合 TimerTask 一起使用,如下所示:
// 1. 定义Timer对象
Timer timer = new Timer();
// 2. 调用schedule设置定时任务TimerTask,以及定时时长,其中TimerTask要重写run()
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时器执行3000ms");
}
}, 3000);其中 TimerTask 实现了 Runnable 接口,本质上就是在 Runnable 的基础上增加了一些属性、方法等,所以定义 TimerTask 时候重写一下其中的 run() 即可!
// 任务类,保存任务以及任务执行时间
class Task implements Comparable<Task> {
private Runnable task;
private long time; // 为了方便判断时间是否到达, 所以保存绝对的时间戳
public Task(Runnable task, long time) {
this.task = task;
this.time = System.currentTimeMillis() + time; // 注意这里存放的是时间戳,所以要计算一下
}
public Runnable getTask() {
return task;
}
public long getTime() {
return time;
}
@Override
public int compareTo(Task o) {
return (int)(this.time - o.time);
}
}
public class MyTimer {
// 使用优先级队列作为存放定时任务的容器,且要以时间间隔最小的任务作为堆顶
private PriorityQueue<Task> qe = new PriorityQueue<>();
// 队列涉及到多线程操作,需要进行加锁
// 并且为了避免“忙等”,可以用wait()和notify()配合,来让出CPU资源
private final Object locker = new Object();
public MyTimer() {
// 构建线程去处理定时任务
Thread thread = new Thread(() -> {
while(true) {
// 加锁
try {
synchronized(locker) {
// 判断是否有定时任务
if(qe.isEmpty() == true) {
// 1. 直接continue会导致忙等,浪费cpu资源
locker.wait();
}
// 走到这说明存在定时任务,则判断是否到达执行时间
Task t = qe.peek();
if(t.getTime() > System.currentTimeMillis()) {
// 2. 时间还没到,直接continue同样会造成忙等,浪费cpu资源,所以这里同样使用wait()等待唤醒
// 不同的是这里要设置超时时间,因为在wait()阻塞到该定时任务时间到之前如果没有新的任务来的话,这个线程
// 都不会被唤醒,导致任务没有及时处理,所以要设置超时时间为剩余等待时间!
long gap = t.getTime() - System.currentTimeMillis();
locker.wait(gap);
} else {
// 时间到了,执行任务
t.getTask().run();
qe.poll();
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
}
// 插入定时任务
public void schedule(Runnable r, long delay) {
synchronized (locker) {
qe.offer(new Task(r, delay));
locker.notify();
}
}
}💥注意事项:
BlockingPriorityQueue,但这里不用它的原因是因为 BlockingPriorityQueue 没有定时阻塞的功能,在构造方法中判断是否到达执行任务的时间,没办法进行定时阻塞,从而没办法实现该逻辑,所以只能用 PriorityQueue 加上 synchronized 来实现! wait() 和 notify() 配合组合和唤醒,避免 CPU 资源浪费!原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。