欢迎光临
我们一直在努力

认识多线程:定时器

一、定时器是什么?

核心定义:定时器是多线程编程中常用的工具,本质是一种「延时执行任务」的机制 —— 允许程序指定某个任务在特定时间后执行,或周期性重复执行。

应用场景
  • 定时备份数据(如每小时备份数据库)
  • 定时发送心跳包(网络通信中维持连接)
  • 延时执行操作(如用户注册后 5 分钟发送验证邮件)
  • 周期性任务(如每隔 30 秒刷新系统状态)
核心特点
  • 异步性:定时器任务的执行不阻塞主线程,独立在后台线程运行
  • 时效性:严格按照预设时间触发(允许轻微误差,受系统调度影响)
  • 独立性:多个定时器任务可并行执行,互不干扰

二、标准库中的定时器(Java 示例)

在 Java 中,JDK 提供了两种常用的定时器实现:java.util.Timer(基础版)和ScheduledExecutorService(推荐,线程池实现,更稳定)。

2.1 Timer 类(简单场景使用)

核心 API:

  • schedule(TimerTask task, long delay):延迟delay毫秒后执行一次任务
  • schedule(TimerTask task, long delay, long period):延迟delay毫秒后,每隔period毫秒重复执行
  • cancel():取消定时器,终止所有任务

代码示例:

import java.util.Timer;

import java.util.TimerTask;

public class TimerDemo {

public static void main(String[] args) {

Timer timer = new Timer();

// 1. 延迟2秒执行一次任务

timer.schedule(new TimerTask() {

@Override

public void run() {

System.out.println("延迟2秒执行的单次任务");

}

}, 2000);

// 2. 延迟1秒后,每隔3秒重复执行任务

timer.schedule(new TimerTask() {

@Override

public void run() {

System.out.println("周期性任务:每隔3秒执行一次");

System.out.println("当前线程:" + Thread.currentThread().getName()); // 输出 Timer-0

}

}, 1000, 3000);

// 主线程继续执行,不阻塞

System.out.println("主线程执行完毕,定时器在后台运行");

}

}

2.2 Timer 的局限性(重要!)
  • 单线程问题:Timer 内部只有一个工作线程,所有任务串行执行。如果某个任务执行时间过长,会阻塞后续任务(例如任务 A 执行了 10 秒,任务 B 本应在 3 秒后执行,实际会延迟到 10 秒后)。
  • 异常崩溃问题:若某个任务抛出未捕获异常,Timer 的工作线程会直接终止,后续所有任务都无法执行。
  • 时间漂移:周期性任务的执行时间是「上一次任务结束时间 + 周期」,而非「上一次任务开始时间 + 周期」,长期运行会导致时间漂移。
2.3 推荐方案:ScheduledExecutorService(线程池实现)

ScheduledExecutorService是 JDK 5 + 引入的,基于线程池,解决了 Timer 的所有缺陷,是生产环境的首选。

核心 API:

  • schedule(Runnable command, long delay, TimeUnit unit):延迟执行单次任务
  • scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):固定频率执行(以上一次任务开始时间为基准)
  • scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit):固定延迟执行(以上一次任务结束时间为基准)

代码示例:

import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;

public class ScheduledExecutorDemo {

public static void main(String[] args) {

// 创建包含2个核心线程的定时线程池

ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

// 1. 延迟2秒执行单次任务

executor.schedule(() -> {

System.out.println("ScheduledExecutor:延迟2秒的单次任务");

}, 2, TimeUnit.SECONDS);

// 2. 固定频率:延迟1秒后,每隔3秒执行一次(不受任务执行时间影响)

executor.scheduleAtFixedRate(() -> {

System.out.println("固定频率任务:每隔3秒执行(以上一次开始时间为准)");

try {

Thread.sleep(1000); // 模拟任务执行1秒

} catch (InterruptedException e) {

e.printStackTrace();

}

}, 1, 3, TimeUnit.SECONDS);

// 3. 固定延迟:延迟1秒后,上一次任务结束后隔2秒执行

executor.scheduleWithFixedDelay(() -> {

System.out.println("固定延迟任务:上一次结束后隔2秒执行");

try {

Thread.sleep(1000); // 模拟任务执行1秒

} catch (InterruptedException e) {

e.printStackTrace();

}

}, 1, 2, TimeUnit.SECONDS);

// 注意:主线程结束后,线程池不会自动关闭,需手动 shutdown

// executor.shutdown(); // 若需主线程结束后终止定时器,可调用

}

}

2.4 两者对比

特性

Timer

ScheduledExecutorService

线程模型

单线程

多线程(线程池)

异常处理

单个任务异常导致整体崩溃

单个任务异常不影响其他任务

周期性任务计时

以上一次结束时间为准

支持固定频率 / 固定延迟两种模式

稳定性

高(生产环境推荐)


三、手动实现一个简单定时器(加深理解)

为了更好地掌握定时器的核心原理,我们手动实现一个基础版本。核心思路:

  • 用「优先级队列」存储待执行任务(按执行时间排序,保证每次取最早要执行的任务)
  • 用一个后台线程循环检查队列,若任务到达执行时间则执行
  • 若队列中无任务或任务未到执行时间,线程阻塞(避免空轮询浪费 CPU)
3.1 核心代码实现

import java.util.PriorityQueue;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.ReentrantLock;

/**

* 手动实现简单定时器

*/

public class MyTimer {

// 1. 任务类:存储任务内容和执行时间(绝对时间:System.currentTimeMillis() + 延迟时间)

static class Task implements Comparable<Task> {

Runnable runnable; // 要执行的任务

long executeTime; // 执行时间(毫秒级时间戳)

public Task(Runnable runnable, long delay) {

this.runnable = runnable;

this.executeTime = System.currentTimeMillis() + delay;

}

// 优先级队列排序规则:执行时间早的任务排在前面

@Override

public int compareTo(Task o) {

return (int) (this.executeTime – o.executeTime);

}

}

// 2. 优先级队列:存储任务(线程安全,需加锁)

private final PriorityQueue<Task> taskQueue = new PriorityQueue<>();

// 3. 锁和条件变量:保证队列操作线程安全,实现线程阻塞/唤醒

private final ReentrantLock lock = new ReentrantLock();

private final Condition condition = lock.newCondition();

// 4. 控制定时器是否运行

private volatile boolean isRunning = true;

// 启动定时器后台线程

public MyTimer() {

new Thread(this::loop, "MyTimer-Worker").start();

}

// 循环检查并执行任务

private void loop() {

while (isRunning) {

lock.lock();

try {

// 若队列无任务,阻塞等待

while (taskQueue.isEmpty()) {

condition.await();

}

// 取队列中最早要执行的任务

Task task = taskQueue.peek();

long now = System.currentTimeMillis();

if (now >= task.executeTime) {

// 任务到达执行时间,执行并从队列移除

taskQueue.poll();

task.runnable.run();

} else {

// 任务未到执行时间,阻塞到执行时间(避免空轮询)

condition.await(task.executeTime – now, TimeUnit.MILLISECONDS);

}

} catch (InterruptedException e) {

e.printStackTrace();

break;

} finally {

lock.unlock();

}

}

}

// 添加任务(延迟delay毫秒执行)

public void schedule(Runnable runnable, long delay) {

lock.lock();

try {

taskQueue.add(new Task(runnable, delay));

condition.signal(); // 唤醒阻塞的工作线程(新任务可能是最早执行的)

} finally {

lock.unlock();

}

}

// 停止定时器

public void stop() {

isRunning = false;

lock.lock();

try {

condition.signal(); // 唤醒工作线程,让其退出循环

} finally {

lock.unlock();

}

}

// 测试

public static void main(String[] args) {

MyTimer timer = new MyTimer();

// 添加任务1:延迟1秒执行

timer.schedule(() -> {

System.out.println("自定义定时器:延迟1秒执行的任务");

System.out.println("执行线程:" + Thread.currentThread().getName()); // MyTimer-Worker

}, 1000);

// 添加任务2:延迟3秒执行

timer.schedule(() -> {

System.out.println("自定义定时器:延迟3秒执行的任务");

timer.stop(); // 执行完毕后停止定时器

}, 3000);

System.out.println("主线程继续执行");

}

}

3.2 实现要点解析
  • 线程安全:优先级队列PriorityQueue是非线程安全的,因此用ReentrantLock保证队列操作(添加、取出)的原子性。
  • 阻塞机制:用Condition的await()实现线程阻塞,避免空轮询(若任务未到执行时间,线程阻塞到执行时间,节省 CPU 资源)。
  • 任务排序:Task实现Comparable接口,优先级队列按执行时间排序,保证每次取出最早要执行的任务。
3.3 局限性(待优化)
  • 单线程执行:所有任务串行,若某个任务执行时间过长,会阻塞后续任务(可通过线程池优化,让任务在不同线程执行)。
  • 不支持周期性任务:当前仅支持单次延迟执行(可扩展Task类,添加周期参数,执行后重新计算下次执行时间并放回队列)。
  • 无异常处理:若任务抛出异常,会导致定时器线程终止(可在task.runnable.run()外层加try-catch捕获异常)。

四、总结

  • 定时器的核心是「延时 / 周期性执行任务」,本质是多线程异步调度的应用。
  • 实际开发中,优先使用ScheduledExecutorService(线程池实现),避免使用Timer(单线程、异常崩溃问题)。
  • 手动实现定时器的核心思路:优先级队列(任务排序)+ 线程阻塞(避免空轮询)+ 线程安全(锁机制)。
  • 进阶方向:可基于手动实现的版本,扩展线程池、周期性任务、异常重试等功能,更贴近生产环境需求。
赞(0)
未经允许不得转载:171主机测评 » 认识多线程:定时器
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址