时间轮算法实现原理,Java Timer 的实现原理,ScheduledThreadPoolExecutor 实现原理

Timer 的实现原理

Timer 通过一个优先级队列存储需要执行的任务,塞入定时任务到优先级队列时,会按照任务到执行时间进行排序。接着 Timer 会依赖一个单线程从队列取任务,如果当前的时间大于定时任务的执行时间,则执行任务,反之则睡眠等待到执行时间。

while (true) {
    // 当前的执行时间
    long currTime = System.currentTimeMillis();
    // 最近的一个任务执行时间
    long execTime = queue.peek().scheduledExecutionTime();
    if (currTime >= execTime) {
        TimerTask task = queue.poll();
        task.run();
    } else {
        // 如果没有任务的执行时间,就一直睡眠等待
        Thread.sleep(execTime - currTime);
    }
}

Timer 的实现方式有着诸多缺点:

  • 单线程导致任务阻塞:如果一个任务执行时间过长,其他任务将无法及时执行。这会影响到所有被调度的任务的执行时间。
  • 单线程导致任务延迟:所有任务都在同一个线程中顺序执行,如果某个任务出现延迟或阻塞,会导致后续任务的延迟。
  • 线程安全问题:TimerTimerTask 并不是线程安全的。在多线程环境中使用时,如果不小心处理,可能会导致并发问题。例如,取消任务和调度新任务可能会产生竞争条件。

ScheduledThreadPoolExecutor 实现原理

ScheduledThreadPoolExecutor 使用 DelayedWorkQueue 作为其任务队列。DelayedWorkQueue 是一个优先级队列,任务根据其执行时间排序。

ScheduledFutureTask 是一个实现了 RunnableScheduledFuture 接口的类,表示一个可以被调度的任务。它包含了任务的执行时间和周期性调度的信息。

ScheduledThreadPoolExecutor 使用 ReentrantLock 代替了 synchronizedwait()notify() 来实现线程同步和任务调度。

ScheduledThreadPoolExecutor 引入了 Leader-Follwer 机制来优化任务调度,同一时间,只会有一个 Leader 在等待头部任务,该线程会等待直到可以执行任务或被中断,当获取到任务后,该线程会将 Leader 的身份转让给其他线程,由新的 Leader 去等待执行任务。

take() 中可以看到 Leader-Follower 机制的实现:

public Runnable take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            RunnableScheduledFuture<?> first = queue[0];
            if (first == null)
                available.await();
            else {
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0)
                    return poll();
                first = null; // don't retain ref while waiting
                if (leader != null)
                    available.await();
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && queue[0] != null)
            available.signal();
        lock.unlock();
    }
}

Time Wheel 实现原理

TimerScheduledThreadPoolExecutor 都依靠优先级队列对定时任务进行重排序,每当塞入新任务时,重排序的工作量太大

时间轮(Time Wheel)是一种高效的定时器实现机制,特别适用于需要处理大量定时任务的场景。它通过将时间分片并使用环形数组来管理定时任务,极大地提高了定时器的性能和可扩展性。时间轮的设计可以有效地解决 TimerScheduledThreadPoolExecutor 的一些问题。

时间轮的核心思想是将时间分成固定大小的时间片,并将这些时间片组织成一个环形结构。每个时间片对应一个桶(bucket),桶中存储需要在该时间片执行的任务。当时间推进时,时间轮的指针(类似于时钟的秒针)依次移动到下一个时间片,检查并执行该时间片中的任务。

public class TimeWheel {
    private final long tickDuration; // 每个时间片的持续时间
    private final int wheelSize; // 时间轮的大小(时间片的数量)
    private final List<Set<Runnable>> wheel; // 时间轮的桶
    private final ScheduledExecutorService executor; // 用于推进时间轮的调度器
    private int currentTick = 0; // 当前时间片

    public TimeWheel(long tickDuration, int wheelSize) {
        this.tickDuration = tickDuration;
        this.wheelSize = wheelSize;
        this.wheel = new ArrayList<>(wheelSize);
        for (int i = 0; i < wheelSize; i++) {
            wheel.add(new HashSet<>());
        }
        this.executor = Executors.newSingleThreadScheduledExecutor();
        this.executor.scheduleAtFixedRate(this::advance, tickDuration, tickDuration, TimeUnit.MILLISECONDS);
    }

    // 添加任务到时间轮中
    public void addTask(Runnable task, long delay) {
        long ticks = delay / tickDuration;
        int bucketIndex = (int) ((currentTick + ticks) % wheelSize);
        wheel.get(bucketIndex).add(task);
    }

    // 推进时间轮
    private void advance() {
        Set<Runnable> tasks = wheel.get(currentTick);
        for (Runnable task : tasks) {
            task.run();
        }
        tasks.clear();
        currentTick = (currentTick + 1) % wheelSize;
    }

    public void shutdown() {
        executor.shutdown();
    }

    public static void main(String[] args) {
        TimeWheel timeWheel = new TimeWheel(1000, 60); // 每个时间片1秒,时间轮大小为60
        timeWheel.addTask(() -> System.out.println("Task executed!"), 5000); // 5秒后执行任务
    }
}

Hierarchical TimeWheel

上面这种单层时间轮的实现方式存在一个很明显的缺陷,就是能处理的时间间隔,就是一个时间片,例如,时间片为 1s,而我想要延迟 1.5s 后执行,就无法实现。

多层时间轮(Hierarchical TimeWheel)是一个常见的改进方案。它通过使用多个层次的时间轮来处理不同粒度的定时任务。每一层时间轮处理不同的时间范围,最底层的时间轮处理最细粒度的时间任务。

  • 顶层时间轮: 处理较大的时间范围。
  • 底层时间轮: 处理较小的时间范围。

当一个任务的延迟时间超过当前时间轮的最大时间片时,可以将任务推进到更高层的时间轮中。

public class HierarchicalTimeWheel {
    private final long tickDuration;
    private final int wheelSize;
    private final List<Set<Runnable>>[] wheels;
    private final ScheduledExecutorService executor;
    private int currentTick = 0;

    public HierarchicalTimeWheel(long tickDuration, int wheelSize, int levels) {
        this.tickDuration = tickDuration;
        this.wheelSize = wheelSize;
        this.wheels = new List[levels];
        for (int i = 0; i < levels; i++) {
            wheels[i] = new ArrayList<>(wheelSize);
            for (int j = 0; j < wheelSize; j++) {
                wheels[i].add(new HashSet<>());
            }
        }
        this.executor = Executors.newSingleThreadScheduledExecutor();
        this.executor.scheduleAtFixedRate(this::advance, tickDuration, tickDuration, TimeUnit.MILLISECONDS);
    }

    public void addTask(Runnable task, long delay) {
        long ticks = delay / tickDuration;
        int level = 0;
        while (ticks >= wheelSize && level < wheels.length - 1) {
            ticks /= wheelSize;
            level++;
        }
        int bucketIndex = (int) ((currentTick + ticks) % wheelSize);
        wheels[level].get(bucketIndex).add(task);
    }

    private void advance() {
        for (int level = 0; level < wheels.length; level++) {
            Set<Runnable> tasks = wheels[level].get(currentTick);
            for (Runnable task : tasks) {
                task.run();
            }
            tasks.clear();
            if (level < wheels.length - 1 && currentTick == 0) {
                wheels[level + 1].get(currentTick).addAll(wheels[level].get(currentTick));
                wheels[level].get(currentTick).clear();
            }
        }
        currentTick = (currentTick + 1) % wheelSize;
    }

    public void shutdown() {
        executor.shutdown();
    }

    public static void main(String[] args) {
        HierarchicalTimeWheel timeWheel = new HierarchicalTimeWheel(1000, 60, 3); // 每个时间片1秒,时间轮大小为60,3层时间轮
        timeWheel.addTask(() -> System.out.println("Task executed!"), 5000); // 5秒后执行任务
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值