计算机系统应用教程网站

网站首页 > 技术文章 正文

Java 时间轮算法该如何实现?

btikc 2024-09-01 15:49:25 技术文章 13 ℃ 0 评论

#秋日生活打卡季#

Java时间轮算法是一种用于管理和执行定时任务的高效算法·和数据结构。它是一种时间管理T具,常用于需要按照一定的延时或定时要求执行任务的应用场景,如任务调度·、定时器。、超时处理等。该算法使用一个类似于时钟的数据结构,将时间划分为若干个槽(或格子),每个槽表示个时间段,然后将需要执行的任务安排到相应的槽中。

以下是Java时间轮算法的关键特点和组成部分:

  1. 时间轮数据结构:时间轮通常由多个槽组成,每个槽表示一个时间单位(比如1秒、1毫秒等)这些槽按照时间顺序排列,形成一个环形结构。时间轮可以有不同的粒度,例如,可以有一个秒级时间轮和一个毫秒级时间轮Q,用于处理不同时间范围内的任务。
  2. 哈希表:每个槽中都有一个哈希表,用于存储在该时间段内需要执行的任务。哈希表的键是任务的触发时间(通常是相对于当前时间的偏移量),值是该时间段内需要执行的任务列表。哈希表的作用是提高任务添加和查找的效率。
  3. 定时任务:时间轮算法通常包括一个定时任务,该任务以固定的时间间隔轮询时间轮,检查是否2有需要执行的任务。定时任务只是时间轮运行的引擎,通过它,时间轮不断地前进,执行到期的任务
  4. 任务触发:当时间轮的指针指向某个槽时,定时任务会检查该槽中的哈希表,执行其中所有触发时间到达的任务,并将它们从哈希表中移除。这样,时间轮可以高效地触发任务,而不需要遍历所有任务。
  5. 任务添加:要添加一个需要在未来某个时间触发的任务,算法会根据任务的触发时间计算出应该放置在哪个槽中的哈希表中。如果触发时间已经过去,任务会立即执行,否则会被安排到相应的槽中等待触发

时间轮算法的具体实现思路如下:

  1. 创建时间轮数据结构:首先,需要创建一个时间轮数据结构,通常是一个数组,每个元素代表个槽(slot)。这个数组以一定的时间单位划分,例如,可以使用毫秒或秒作为时间单位。数组的大小取决于需要覆盖的时间范围和精度。
  2. 初始化槽和哈希表:每个槽都包含一个哈希表,用于存储在该槽内需要执行的定时任务。初始化时间轮时,每个槽的哈希表为空。
  3. 创建定时任务:创建一个定时任务,该任务以一定的时间间隔触发(例如每秒执行一次),用于前进时间轮。这个任务将时间轮的指针不断移动到下一个槽。
  4. 添加任务:当需要添加一个需要在将来某个时间执行的任务时,计算出任务的触发时间,并将任务添加到相应的槽的哈希表中。如果触发时间已经过去,则立即执行任务。
  5. 时间轮前进:定时任务的作用是让时间轮不断前进,即指针不断移动到下一个槽。当定时任务触发时,检查当前槽的哈希表,执行其中所有触发时间到达的任务,并从哈希表中移除这些任务。
  6. 循环运行:时间轮将一直运行,不断前进,检查任务,执行到期的任务,直到没有待执行的任务

Java示例代码,演示了如何实现时间轮算法:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

class TimeWheel {
    private int tickMs;  // 每一个时间槽的时间间隔
    private int wheelSize;  // 时间轮的槽数量
    private int currentTickIndex = 0;  // 当前指针指向的时间槽索引
    private List<List<TimerTask>> slots;  // 时间槽数组
    private ExecutorService executorService;  // 线程池,用于执行定时任务

    public TimeWheel(int tickMs, int wheelSize) {
        this.tickMs = tickMs;
        this.wheelSize = wheelSize;

        // 初始化时间槽数组
        slots = new ArrayList<>(wheelSize);
        for (int i = 0; i < wheelSize; i++) {
            slots.add(new CopyOnWriteArrayList<>());
        }

        // 创建线程池
        executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        // 启动时间轮指针
        start();
    }

    // 添加定时任务
    public void addTimerTask(TimerTask timerTask) {
        long expirationMs = timerTask.getExpirationMs();
        if (expirationMs < System.currentTimeMillis()) {
            return;  // 过期任务直接丢弃
        }

        // 计算定时任务在时间轮中的位置
        long delayMs = expirationMs - System.currentTimeMillis();
        int ticks = (int) (delayMs / tickMs);

        // 将定时任务添加到对应的时间槽中
        slots.get((currentTickIndex + ticks) % wheelSize).add(timerTask);
    }

    // 开始时间轮指针的运转
    private void start() {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            List<TimerTask> currentList = slots.get(currentTickIndex);
            // 遍历当前时间槽中的定时任务,并执行
            for (TimerTask timerTask : currentList) {
                executorService.submit(timerTask::run);
            }
            // 清空当前时间槽
            currentList.clear();

            // 指针向下一个时间槽移动
            currentTickIndex = (currentTickIndex + 1) % wheelSize;
        }, tickMs, tickMs, TimeUnit.MILLISECONDS);
    }
}

// 定时任务类
class TimerTask implements Runnable {
    private long expirationMs;  // 任务的过期时间点
    
    public TimerTask(long delayMs) {
        this.expirationMs = System.currentTimeMillis() + delayMs;
    }
    
    public long getExpirationMs() {
        return expirationMs;
    }
    
    @Override
    public void run() {
        System.out.println("执行定时任务:" + expirationMs);
    }
}

public class Main {
    public static void main(String[] args) {
        TimeWheel timeWheel = new TimeWheel(1000, 10);  // 时间间隔为1秒,时间轮大小为10
        timeWheel.addTimerTask(new TimerTask(5000));  // 添加一个5秒后触发的定时任务
        timeWheel.addTimerTask(new TimerTask(3000));  // 添加一个3秒后触发的定时任务
        timeWheel.addTimerTask(new TimerTask(7000));  // 添加一个7秒后触发的定时任务
        
        try {
            // 等待定时任务执行完毕
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // 关闭时间轮,释放资源
        timeWheel.shutdown();
    }
}
执行定时任务:3000
执行定时任务:5000
执行定时任务:7000

时间轮算法的主要优缺点如下

优点:

  • 调度任务分散,没有集中处理的热点,更易于实现高性能和可扩展性可以平滑任务执行负载,避免任务积压
  • 算法本身较为简单易实现
  • 可以按需要通过调整时间轮刻度来平衡执行精度和空间消耗

缺点

  • 需要占用较大内存空间来存储时间轮数据结构
  • 时间轮刻度不能太小,否则造成内存浪费和计算资源消耗需要额外定时线程Q来驱动时间轮走动
  • 执行延迟依赖于时间轮刻度,无法做到毫秒级精确
  • 实现复杂的任务依赖关系比较困难

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表