一、自定义线程池
package org.example.n8;
import lombok.extern.slf4j.Slf4j;
import java.sql.Connection;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.TestPool")
public class TestPool {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1,
(queue, task)->{
// 1)死等
// queue.put( task);
// 2)带超时等待
// queue.offer( task, 500, TimeUnit.MILLISECONDS);
// 3)让调用者放弃任务执行
// log.debug("放弃任务:{}", task);
// 4)让调用者抛出异常
// throw new RuntimeException("任务执行失败:" + task);
// 5)让调用者自己执行任务
task.run();
});
for (int i = 0; i < 4; i++) {
int j= i;
threadPool.execute(() -> {
try {
Thread.sleep(1500L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("{}", j);
});
}
}
}
@FunctionalInterface
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue, T task);
}
@Slf4j(topic = "c.ThreadPool")
class ThreadPool {
// 任务队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers = new HashSet<>();
// 核心线程数
private int coreSize;
// 获取线程的超时时间
private long timeout;
private TimeUnit timeUnit;
// 拒绝策略
private RejectPolicy<Runnable> rejectPolicy;
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int capacity, RejectPolicy<Runnable> rejectPolicy) {
taskQueue = new BlockingQueue<>(capacity);
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.rejectPolicy = rejectPolicy;
}
public void execute(Runnable task) {
// 当任务数没有达到核心线程数时,新创建一个线程
synchronized ( workers){
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
log.debug("新增worker:{}, {}", worker, task);
workers.add(worker);
worker.start();
} else {
// 添加任务
// taskQueue.put(task);
// 1)死等
// 2)待超时等待
// 3)让调用者放弃任务执行
// 4)让调用者抛出异常
// 5)让调用者自己执行任务
taskQueue.tryPut(rejectPolicy, task);
}
}
}
class Worker extends Thread{
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
// 执行任务
// 1)当task不为null,执行任务
// 2)当task执行完毕,从任务队列中获取任务
// while (task != null || (task = taskQueue.take()) != null) {
while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
try {
log.debug("正在执行任务.....{}", task);
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
synchronized ( workers){
log.debug("worker被移除:{}", this);
workers.remove(this);
}
}
}
}
@Slf4j(topic = "c.BlockingQueue")
class BlockingQueue<T>{
// 1.任务队列
private Deque<T> queue = new ArrayDeque<>();
// 2.锁,用于在任务队列中锁住对头和队尾
private ReentrantLock lock = new ReentrantLock();
// 3.生产者条件变量
private Condition fullWaitSet = lock.newCondition();
// 4.消费者条件变量
private Condition emptyWaitSet = lock.newCondition();
// 5.容量
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
// 超时的阻塞获取
public T poll(long timeout, TimeUnit unit) {
lock.lock();
try {
// 将时间统一转化为毫秒
long nanos = unit.toNanos(timeout);
while (queue.isEmpty()) {
try {
nanos = emptyWaitSet.awaitNanos(nanos);
if (nanos <= 0) {
return null;
}
emptyWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
// 阻塞获取
public T take() {
lock.lock();
try {
while (queue.isEmpty()) {
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
// 阻塞添加
public void put(T task) {
lock.lock();
try {
while (queue.size() == capacity) {
try {
log.debug("等待加入任务队列:{}", task);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列:{}", task);
queue.addLast(task);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
// 带超时时间的阻塞式添加
public boolean offer(T task, long timeout, TimeUnit unit) {
lock.lock();
try {
long nanos = unit.toNanos(timeout);
while (queue.size() == capacity) {
try {
log.debug("等待加入任务队列:{}", task);
if(nanos <= 0){
return false;
}
nanos = fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列:{}", task);
queue.addLast(task);
emptyWaitSet.signal();
return true;
}finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return queue.size();
}finally {
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
// 判断队列是否已满
if (queue.size() == capacity) {
rejectPolicy.reject(this, task);
}else { // 有空闲
log.debug("加入任务队列:{}", task);
queue.addLast(task);
emptyWaitSet.signal();
}
}finally {
lock.unlock();
}
}
}
二、线程池
1.两种线程池
ThreadPoolExecutor:线程池
ScheduleThreadPoolExecutor:带调度功能的线程池

2. ThreadPoolExecutor 线程池
2.1线程池状态
线程池的5中状态:
RUNNING:运行状态,线程池创建好之后就会进入此状态,如果不手动调用关闭方法,那么线程池在整个程序运行期间都是此状态。
SHUTDOWN:关闭状态,不再接受新任务提交,但是会将已保存在任务队列中的任务处理完。
STOP:停止状态,不再接受新任务提交,并且会中断当前正在执行的任务、放弃任务队列中已有的任务。
TIDYING:整理状态,所有的任务都执行完毕后(也包括任务队列中的任务执行完),当前线程池中的活动线程数降为 0 时的状态。到此状态之后,会调用线程池的 terminated() 方法。
TERMINATED:销毁状态,当执行完线程池的 terminated() 方法之后就会变为此状态。
2.2构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
线程池的工作方式:
2.3JDK提供的线程池,通过Executors工具类新建
2.3.1.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点:
- 核心线程数 = 最大线程数 = nThreads(固定线程数量,无临时线程)。
- 空闲线程不会被销毁(
keepAliveTime = 0)。 - 任务队列是
LinkedBlockingQueue(无界队列)。
用途:
适用于任务数量已知、耗时相对均匀的场景,如服务器接收固定数量的并发请求(避免线程频繁创建 / 销毁的开销)。
- 示例:处理数据库连接池的并发查询(线程数可设为 CPU 核心数的 2~4 倍)。
注意:
无界队列可能导致任务堆积过多,引发 OOM(内存溢出),需控制任务提交速度。
2.3.2.newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点:
- 核心线程数 = 0,最大线程数 =
Integer.MAX_VALUE(理论上可创建无限多临时线程)。 - 临时线程空闲 60 秒后自动销毁(
keepAliveTime = 60s)。 - 任务队列是
SynchronousQueue(同步队列,不存储任务,直接传递给线程)。
用途:
适用于任务数量不确定、耗时短、并发峰值高的场景,如临时突发的短任务(快速处理后释放资源)。
- 示例:Web 服务器处理瞬时高并发的短请求(如查询接口)。
注意:
最大线程数无上限,若任务执行时间过长,可能创建大量线程导致 CPU / 内存耗尽,需谨慎使用。
SynchronousQueue,同步队列容量为0,也就是说想队列中添加元素的时候,如果此时没有线程消费元素,添加元素的线程会阻塞。
2.3.3.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程 也不会被释放。
区别:
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一 个线程,保证池的正常工作
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因 此不能调用 ThreadPoolExecutor 中特有的方法
Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
2.4提交任务
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
2.5关闭线程池
ThreadPoolExecutor 提供了两个核心关闭方法:shutdown() 和 shutdownNow(),以及一个辅助方法 awaitTermination() 用于等待关闭完成。
2.5.1shutdown()方法
线程状态由RUNNING变为SHUTDOWN
核心行为:
- 拒绝接收新任务(新任务提交会触发
RejectedExecutionException)。 - 继续执行正在运行的任务和等待队列中已提交的任务。
- 所有任务执行完毕后,自动终止所有工作线程,线程池状态变为
TERMINATED。
适用场景:希望等待已有任务(包括队列中的任务)完成后再关闭,适用于允许一定关闭时间的场景(如应用正常停机)。
代码事例
package org.example.n8;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.TestShutDown")
public class TestShutDown {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(() -> {
log.debug("Task1 begin---- ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("Task1 end---- ");
});
executorService.execute(() -> {
log.debug("Task2 begin---- ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("Task2 end---- ");
});
executorService.execute(() -> {
log.debug("Task3 begin---- ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("Task3 end---- ");
});
log.debug("shutdown----");
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.SECONDS);
log.debug("other----");
}
}

2.5.2shutdownNow()方法
线程状态由RUNNING变为STOP
核心行为:
- 拒绝接收新任务。
- 尝试中断正在运行的任务(通过
Thread.interrupt(),但仅对可中断的任务有效,如sleep()、wait()等阻塞操作)。 - 清空等待队列,返回未执行的任务列表(可用于记录或重试)。
- 尽快终止所有工作线程,线程池状态最终变为
TERMINATED。
注意:
- 中断正在运行的任务不一定能立即停止(若任务不响应中断,可能继续执行)。
- 未执行的任务会被从队列中移除,需手动处理(如记录日志、后续重试)。
适用场景:需要立即关闭线程池,不等待队列任务(如应用紧急停机、任务超时强制终止)。
当线程池调用 shutdownNow() 后,若存在未处理中断逻辑的线程(即线程持续执行任务,不响应中断),那么该线程依然后执行,那么线程池处在stop状态,若该线程一直不结束,则线程池会一直处于stop状态。
2.6异步模式之工作线程
工作线程模式是异步模式的 “实现方案之一”,异步模式是 “设计思想”,工作线程模式是 “落地工具”。
异步模式(设计思想)
异步模式是一种任务执行与结果获取分离的设计思想:
- 发起任务后,调用方无需阻塞等待任务完成,可以继续执行其他操作;
- 任务执行完成后,通过回调函数、Future、事件通知等方式,将结果反馈给调用方(或直接处理结果);
- 核心目标:提高系统吞吐量(避免线程阻塞浪费资源),提升响应速度。
工作线程实现方案
工作线程模式(也叫 “线程池模式”)是一种基于线程池的任务分发与执行机制:
- 提前创建一组固定 / 动态的 “工作线程”(Worker Thread),并维护一个任务队列;
- 主线程(或任务提交线程)仅负责将任务放入队列,不直接执行任务;
- 工作线程持续从队列中获取任务并执行,执行完成后无需销毁,继续等待下一个任务;
- 核心目标:避免线程频繁创建 / 销毁的开销,控制并发数,提高任务执行效率。
示例:
- 餐厅的 “后厨团队”(工作线程池),前台服务员(主线程)接到订单(任务)后,只需将订单放入后厨队列,无需自己做菜;
- 后厨厨师(工作线程)轮流取订单做菜,做完一道再取下一道,这就是工作线程模式。
二者的关系:工作线程模式是异步模式的 “最优解” 之一
异步模式的核心是 “不阻塞调用方”,而工作线程模式恰好能高效实现这一点,原因如下:
- 调用方无阻塞:任务提交方(如主线程)将任务放入队列后立即返回,无需等待任务执行,符合异步 “非阻塞” 的核心要求;
- 任务并发执行:工作线程池中的多个线程并行处理任务,提高了任务执行效率,契合异步模式 “提高吞吐量” 的目标;
- 资源可控:通过线程池控制最大并发数,避免因创建大量线程导致的资源耗尽,让异步任务的执行更稳定。
典型场景:
- 后端接口处理异步任务(如发送短信、生成报表):接口接收请求后,将任务提交给线程池(工作线程模式),立即返回 “任务已受理”(异步响应),线程池后台执行任务,执行完成后通过消息队列或数据库记录结果;
- 前端异步请求:浏览器发起 AJAX 请求(异步模式),后端服务器通过线程池(工作线程模式)处理请求,避免主线程阻塞。
2.7线程池创建多少线程合适
2.7.1CPU密集型任务
特点:任务主要消耗 CPU 资源(如数据计算、排序、加密),线程几乎不阻塞(IO 耗时占比 < 10%)。
核心原则:线程数 ≈ CPU 核心数,避免线程切换开销(切换一次耗时约 1-10 微秒,频繁切换会抵消多线程优势)。
计算公式:线程数 = CPU 核心数 ± 1(+1 是为了应对单个线程偶尔的阻塞,避免 CPU 空闲)。
示例:8 核 CPU 服务器 → 线程数设为 8 或 9。
2.7.2IO 密集型任务
特点:任务大部分时间在等待 IO 操作(如数据库查询、网络请求、文件读写),线程阻塞时间长(IO 耗时占比 > 50%)。
核心原则:线程数 > CPU 核心数,利用 IO 等待时间让其他线程执行任务,提高 CPU 利用率。
计算公式:线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间
示例:4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 cpu 被 100% 利用,套用公式
2.7.3混合任务(既有 CPU 计算也有 IO 等待)
核心原则:先拆分任务类型,分别按上述公式计算,再取折中值;或通过压测调整。
简化方案:按 IO 密集型公式计算(因混合任务中 IO 等待通常占比更高),再根据实际压测结果微调。
2.7.4关键影响因素(实际调整依据)
公式是基础,生产环境需结合以下因素修正
-
CPU 核心数(物理核心,非逻辑核心)
- 查看方式:Linux 执行
lscpu(看CPU(s):对应的物理核心数,排除超线程);Windows 任务管理器 → 性能 → CPU → 核心数。 - 注意:超线程(HT)会让逻辑核心数翻倍,但对 CPU 密集型任务提升有限,线程数仍按物理核心计算。
- 查看方式:Linux 执行
-
任务的 IO 等待占比
- 若 IO 等待时间极长(如调用第三方接口耗时 1 秒,CPU 执行仅 10 毫秒),可进一步增加线程数(如 CPU 核心数 × 5~10),但需控制上限(避免线程过多导致内存溢出)。
- 可通过压测工具(如 JMeter)统计 “线程阻塞时间” 和 “执行时间”,精准计算比例。
-
服务器其他进程的 CPU 占用
- 若服务器同时运行数据库、缓存等其他服务,需预留 20%-30% CPU 资源,线程数 = (可用 CPU 核心数)× 公式系数。
- 示例:8 核 CPU 中,数据库占用 4 核 → 可用核心数按 5 核计算。
-
线程池的任务队列大小
- 线程数与队列大小需配合:IO 密集型任务队列可设为中等大小(如 100-1000),避免队列过长导致任务堆积;CPU 密集型任务队列可设为较小值(如 10-50),让线程快速处理任务。
-
内存限制
- 每个线程默认栈大小为 1MB(可通过
-Xss调整),线程数过多会消耗大量内存(如 1000 个线程 ≈ 1GB 栈内存)。 - 需确保线程数 × 线程栈大小 + 任务内存占用 ≤ 服务器可用内存,避免 OOM。
- 每个线程默认栈大小为 1MB(可通过
2.7.5生产环境推荐步骤(落地流程)
-
第一步:获取基础参数
- 服务器物理 CPU 核心数(记为 N)。
- 任务类型(判断是 CPU 密集型还是 IO 密集型)。
-
第二步:初步计算线程数
- CPU 密集型:N ± 1。
- IO 密集型:N × 2 ~ N × 4。
-
第三步:压测验证与调整
- 用压测工具(JMeter、Gatling)模拟生产并发量,观察指标:
- CPU 利用率:目标 70%-80%(过高会导致系统响应变慢,过低说明线程数不足)。
- 任务响应时间:是否在可接受范围内(如接口响应 < 500ms)。
- 线程池队列堆积:是否有大量任务排队(队列长度持续增长说明线程数不足,需增加)。
- 调整规则:
- CPU 利用率 < 50% + 任务排队 → 增加线程数。
- CPU 利用率 > 90% + 响应时间变长 → 减少线程数。
- 内存占用过高 → 减少线程数或调小线程栈大小(
-Xss512k)。
- 用压测工具(JMeter、Gatling)模拟生产并发量,观察指标:
-
第四步:预留冗余
- 最终线程数 = 压测最优值 × 0.8(预留 20% 资源应对流量峰值)。
2.8任务调度线程池
2.8.1.Timer
在任务调度线程池功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但 由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个 任务的延迟或异常都将会影响到之后的任务。
核心功能
Timer 的核心作用是按计划执行任务,支持两种调度方式:
- 一次性任务:在指定的延迟时间后执行一次(如 3 秒后执行某个任务)。
- 周期性任务:在指定延迟后开始,之后按固定间隔重复执行(如延迟 1 秒后,每 5 秒执行一次)。
核心组件
Timer 的使用依赖两个核心类:
Timer:负责调度和管理任务,内部维护一个后台线程(TimerThread)和一个优先级任务队列(TaskQueue),按时间顺序执行任务。TimerTask:抽象类,代表要执行的任务,需要重写run()方法定义任务逻辑,实现了Runnable接口。
常用方法
Timer 提供了调度任务的关键方法,主要分为两类:
调度一次性任务
// 在 delay 毫秒后执行 task
void schedule(TimerTask task, long delay);
// 在指定时间 time 执行 task(若 time 已过去,则立即执行)
void schedule(TimerTask task, Date time);
调度周期性任务
// 延迟 delay 毫秒后,每隔 period 毫秒重复执行 task(以上一次任务开始时间为基准)
void schedule(TimerTask task, long delay, long period);
// 在指定时间 time 开始,之后每隔 period 毫秒重复执行 task
void schedule(TimerTask task, Date time, long period);
// 延迟 delay 毫秒后,每隔 period 毫秒重复执行 task(以上一次任务结束时间为基准)
void scheduleAtFixedRate(TimerTask task, long delay, long period);
schedule 与 scheduleAtFixedRate 的区别:
schedule:周期以 “上一次任务开始时间” 为基准(若任务执行时间超过周期,下一次会立即执行)。scheduleAtFixedRate:周期以 “上一次任务结束时间” 为基准(严格保证固定间隔,若任务执行超时,会追赶执行以弥补延迟)。
使用示例
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
public static void main(String[] args) {
Timer timer = new Timer(); // 创建 Timer 实例(启动后台线程)
// 1. 一次性任务:3 秒后执行
TimerTask oneTimeTask = new TimerTask() {
@Override
public void run() {
System.out.println("一次性任务执行:" + System.currentTimeMillis());
}
};
timer.schedule(oneTimeTask, 3000); // 延迟 3000 毫秒(3 秒)
// 2. 周期性任务:延迟 1 秒后,每 2 秒执行一次
TimerTask periodicTask = new TimerTask() {
private int count = 0;
@Override
public void run() {
count++;
System.out.println("周期性任务执行(第 " + count + " 次):" + System.currentTimeMillis());
if (count >= 3) {
this.cancel(); // 执行 3 次后取消当前任务
timer.cancel(); // 取消 Timer(停止所有任务)
}
}
};
timer.schedule(periodicTask, 1000, 2000); // 延迟 1 秒,周期 2 秒
}
}
执行结果:
周期性任务执行(第 1 次):1696300000000
一次性任务执行:1696300002000
周期性任务执行(第 2 次):1696300002000
周期性任务执行(第 3 次):1696300004000
底层原理
Timer 的工作机制如下:
Timer实例创建时,会启动一个后台线程(TimerThread),该线程循环读取任务队列(TaskQueue)。- 任务队列按任务的 “下次执行时间” 排序(优先级队列),
TimerThread会阻塞等待直到下一个任务的执行时间。 - 到达执行时间后,
TimerThread取出任务并执行其run()方法,然后更新周期性任务的下次执行时间并重新加入队列。
优缺点与适用场景
优点
- 用法简单,适合轻量级、低频率的定时任务(如简单的延迟操作、周期性日志打印)。
- 无需依赖第三方库,JDK 原生支持。
缺点
- 单线程执行:所有任务由同一个后台线程执行,若某个任务执行时间过长(如阻塞),会阻塞后续所有任务(导致延迟)。
- 不支持并发:无法利用多核 CPU,任务间相互影响(一个任务异常会导致整个
Timer崩溃)。 - 时间精度较低:依赖系统时间和线程调度,不适用于高精度定时场景。
- 任务取消机制有限:
TimerTask.cancel()仅取消单个任务,Timer.cancel()会终止所有任务,但可能存在线程安全问题。
适用场景
- 简单的定时需求(如延迟几秒后关闭资源、低频率的周期性检查)。
- 对并发和时间精度要求不高的场景。
2.8.2newScheduledThreadPool
java.util.concurrent.Executors.newScheduledThreadPool(int corePoolSize) 是 Java 提供的定时任务线程池,专门用于调度延迟任务或周期性任务,底层基于 ScheduledThreadPoolExecutor 实现(继承自 ThreadPoolExecutor),支持多线程并发执行定时任务,解决了 Timer 单线程阻塞的问题。
2.8.2.1.newScheduledThreadPool 线程池的核心特性
核心线程数固定:通过参数 corePoolSize 指定核心线程数(不会被回收),非核心线程(若有)在空闲时会被回收(默认空闲时间 10 秒)。
支持定时任务:提供延迟执行、周期性执行两种调度方式,任务按时间顺序加入队列,由线程池中的线程并发执行。
基于延迟队列:内部使用 DelayedWorkQueue(延迟队列)存储任务,按任务的 “下次执行时间” 排序,确保任务在指定时间被触发。
多线程并发:多个任务可由不同线程并行执行,避免单线程阻塞(区别于 Timer)。
2.8.2.2.核心调度方法
newScheduledThreadPool 返回 ScheduledExecutorService 接口,该接口提供 4 个核心调度方法,按功能分为 “延迟执行” 和 “周期性执行” 两类:
延迟执行(一次性任务)
在指定延迟时间后执行一次任务,无返回值或有返回值(通过 Future 获取)。
| 方法签名 | 功能描述 |
|---|---|
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) | 延迟 delay 时间后,执行 Runnable 任务(无返回值),返回 ScheduledFuture 用于取消任务。 |
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) | 延迟 delay 时间后,执行 Callable 任务(有返回值),返回 ScheduledFuture 用于获取结果或取消任务。 |
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); // 核心线程数 2
// 1. 延迟 3 秒执行 Runnable 任务(无返回值)
executor.schedule(() -> {
System.out.println("3秒后执行的任务(Runnable)");
}, 3, TimeUnit.SECONDS);
// 2. 延迟 2 秒执行 Callable 任务(有返回值)
ScheduledFuture<String> future = executor.schedule(() -> {
System.out.println("2秒后执行的任务(Callable)");
return "任务完成";
}, 2, TimeUnit.SECONDS);
// 获取 Callable 任务的结果(会阻塞直到任务完成)
try {
String result = future.get();
System.out.println("Callable 结果:" + result);
} catch (Exception e) {
e.printStackTrace();
}
周期性执行(重复任务)
延迟指定时间后开始执行,之后按固定周期重复执行,核心区别在于周期的计算基准(以上一次任务开始时间或结束时间为准)。
| 方法签名 | 功能描述 |
|---|---|
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) | 延迟 initialDelay 后开始执行,之后以上一次任务开始时间为基准,每隔 period 时间重复执行。若任务执行时间超过 period,下一次任务会立即执行(追赶执行)。 |
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) | 延迟 initialDelay 后开始执行,之后以上一次任务结束时间为基准,每隔 delay 时间重复执行。无论任务执行多久,两次任务的间隔始终为 delay(不追赶)。 |
2.8.2.3.异常处理机制
定时线程池的异常处理需注意:任务执行时的异常会导致该周期性任务终止(后续不再执行),且异常默认会被封装到 ScheduledFuture 中(若未处理会被吞噬)。
异常对任务的影响
- 一次性任务:异常仅导致当前任务失败,不影响其他任务。
- 周期性任务:若某次执行抛出未捕获异常,该任务会被从线程池移除,后续周期不再执行(这是最常见的 “定时任务突然停止” 原因)。
处理策略(核心:主动捕获异常)
必须在任务内部用 try-catch 捕获所有异常,避免周期性任务终止,同时记录日志便于排查。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
// 周期性任务:每 2 秒执行一次,模拟偶尔抛出异常
ScheduledFuture<?> future = executor.scheduleAtFixedRate(() -> {
try {
System.out.println("周期性任务执行:" + LocalTime.now());
// 模拟 50% 概率抛出异常
if (Math.random() > 0.5) {
throw new RuntimeException("任务执行失败");
}
} catch (Exception e) {
// 关键:捕获异常并记录日志,避免任务终止
System.err.println("任务异常,但继续执行:" + e.getMessage());
}
}, 1, 2, TimeUnit.SECONDS);
执行结果:
周期性任务执行:10:00:00
周期性任务执行:10:00:02
任务异常,但继续执行:任务执行失败
周期性任务执行:10:00:04 // 异常后仍继续执行
周期性任务执行:10:00:06
...
若未捕获异常,任务会在第一次异常后终止,不再执行。
处理 ScheduledFuture 中的异常
若任务内部未捕获异常,异常会被封装到 ScheduledFuture 中,需调用 get() 方法触发异常(适用于一次性任务):
ScheduledFuture<?> future = executor.schedule(() -> {
throw new RuntimeException("一次性任务失败");
}, 1, TimeUnit.SECONDS);
try {
future.get(); // 调用 get() 时,异常以 ExecutionException 抛出
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
System.err.println("捕获到任务异常:" + e.getCause().getMessage());
}
2.8.2.4.关闭线程池
定时线程池的关闭方法与普通线程池一致,需调用 shutdown() 或 shutdownNow(),并建议配合 awaitTermination() 等待关闭完成:
executor.shutdown(); // 启动关闭,不再接收新任务,执行完已有任务
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 超时未关闭则强制终止
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
3.Fork 与 Join
3.1.概念
3.2.使用例子
package org.example.n8;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class TestForkJoin {
public static void main(String[] args) {
// 创建线程池对象
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println(pool.invoke(new MyTask(5)));
}
}
// 建立任务对象
// 若任务有返回值,则继承RecursiveTask
// 若任务无返回值,则继承RecursiveAction
@Slf4j(topic = "c.MyTask")
class MyTask extends RecursiveTask<Integer>{
private int n;
public MyTask(int n) {
this.n = n;
}
@Override
public String toString() {
return "{" + n + '}';
}
@Override
protected Integer compute() {
// 如果 n 已经为 1,可以求得结果了
if (n == 1) {
log.debug("join() {}", n);
return n;
}
// 将任务进行拆分(fork)
MyTask t1 = new MyTask(n - 1);
t1.fork();
log.debug("fork() {} + {}", n, t1);
// 合并(join)结果
int result = n + t1.join();
log.debug("join() {} + {} = {}", n, t1, result);
return result;
}
}
1112

被折叠的 条评论
为什么被折叠?



