深入剖析Netty之定时任务实现

Github地址:https://github.com/lhj502819/netty/tree/v502819-main,示例代码在example模块中

系列文章

上篇文章中我们讲解NioEventLoop时,我们提到了Netty中的任务分为普通任务和定时任务,我们可以通过EventLoop创建一个定时任务,使用方法我们就不在这里讲解了,没使用过的小伙伴可以去度娘找一个Demo看一看,今天这篇文章我们主要讲解Netty的定时任务实现机制。

Java中的定时任务

我们知道在JDK1.5之后提供了定时任务的接口抽象ScheduledExecutorService以及实现ScheduledThreadPoolExecutor,Netty并没有直接使用JDK提供的定时任务实现,而是基于ScheduledExecutorService接口进行了自定义实现。首先我们先来看下ScheduledExecutorService都定义了哪些基础方法。

/**
 * 延迟执行,并不会周期性执行
 * @param callable 延迟执行的任务
 * @param delay 延迟时间
 * @param unit 延迟时间单位
 */
public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);

/**
 * 延迟执行,并不会周期性执行
 * @param callable 延迟执行的任务
 * @param delay 延迟时间
 * @param unit 延迟时间单位
 */
public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);

/**
 * 在延迟initialDelay后定时执行任务
 * @param command 要执行的任务
 * @param initialDelay 初始化延迟
 * @param period 两次任务开始执行的延迟
 * @param unit 时间单位
 * @return
 */
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit);
/**
 * 在延迟initialDelay后定时执行任务
 * @param command 要执行的任务
 * @param initialDelay 初始化延迟
 * @param delay 下一次任务开始距前一次任务结束的时间
 * @param unit 时间单位
 * @return
 */
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay,
                                                 long delay,
                                                 TimeUnit unit);


我们就不对ScheduledThreadPoolExecutor进行讲解了,不能跑题,还是来讲解Netty是如何做的。


核心类

  • ScheduledFutureTask:实现定时任务的核心类
  • AbstractScheduledEventExecutor:集成ScheduledFutureTaskScheduledExecutorService进行实现,进行行为控制

Netty实现定时任务的核心类就是这两个,我们先来分析下ScheduledFutureTask

ScheduledFutureTask

ScheduledFutureTask的主要职责就是判断是否应该要执行定时任务以及执行定时任务。

构造方法

/**
 * @param executor 执行任务的Executpr
 * @param runnable 任务
 * @param nanoTime 任务的执行时间
 */
ScheduledFutureTask(AbstractScheduledEventExecutor executor,
                    Runnable runnable, long nanoTime) {

    super(executor, runnable);
    deadlineNanos = nanoTime;
    //默认两个任务之间没有延迟
    periodNanos = 0;
}

/**
 * @param executor 执行任务的Executor
 * @param runnable 任务
 * @param nanoTime 任务的执行时间
 * @param period 两个任务之间的执行延迟
 */
ScheduledFutureTask(AbstractScheduledEventExecutor executor,
                    Runnable runnable, long nanoTime, long period) {

    super(executor, runnable);
    deadlineNanos = nanoTime;
    periodNanos = validatePeriod(period);
}

成员变量

/**
 * 定时任务的初始化时间
 */
private static final long START_TIME = System.nanoTime();

/**
 * 获得当前距初始化时间的间隔
 */
static long nanoTime() {
    return System.nanoTime() - START_TIME;
}

/**
 * 获得任务的执行时间,相对START_TIME的
 */
static long deadlineNanos(long delay) {
    long deadlineNanos = nanoTime() + delay;
    // Guard against overflow
    return deadlineNanos < 0 ? Long.MAX_VALUE : deadlineNanos;
}

static long initialNanoTime() {
    return START_TIME;
}

// set once when added to priority queue
/**
 * 任务ID
 */
private long id;

/**
 * 任务的执行时间
 */
private long deadlineNanos;
/* 0 - no repeat, >0 - repeat at fixed rate, <0 - repeat with fixed delay */
/**
 * 两个任务之间的执行延迟
 */
private final long periodNanos;

主要方法

取消任务

public boolean cancel(boolean mayInterruptIfRunning) {
    boolean canceled = super.cancel(mayInterruptIfRunning);
    if (canceled) {
        scheduledExecutor().removeScheduled(this);
    }
    return canceled;
}

具体的细节这里就不展开讲解了,主要逻辑就是进行事件通知并将任务从任务队列中移除。

核心逻辑

核心逻辑在#run方法中,主要逻辑就是判断自身是否达到了执行的时间,并执行,需具体代码如下:

public void run() {
        assert executor().inEventLoop();
        try {
            if (delayNanos() > 0L) {
                // 大于零表示没过期
                // Not yet expired, need to add or remove from queue
                if (isCancelled()) {
                    //如果已经取消,则从定时任务队列中移除当前任务
                    scheduledExecutor().scheduledTaskQueue().removeTyped(this);
                } else {
                    //则将当前任务添加到定时任务队列中
                    //后续会有EventLoop轮询队列中的定时任务是否该执行,我们在前边的文章中讲过
                    scheduledExecutor().scheduleFromEventLoop(this);
                }
                return;
            }
            ////////////////////////任务过期//////////////////////////////////////
            //如果执行两个任务的执行延迟为0
            if (periodNanos == 0) {
                if (setUncancellableInternal()) {
                    V result = runTask();
                    setSuccessInternal(result);
                }
            } else {
                //两个任务的执行延迟大于0 ,判断任务是否已经取消
                // check if is done as it may was cancelled
                if (!isCancelled()) {
                    //如果任务没有被取消,则执行任务
                    runTask();
                    if (!executor().isShutdown()) {
                        if (periodNanos > 0) {
                            //重新设置下一次的执行时间,任务开始执行时间 + 两个任务的延迟时间
                            deadlineNanos += periodNanos;
                        } else {
                            //如果两次执行间隔小于0,负负得正获得新的执行时间
                            //设置执行时间为当前时间
                            deadlineNanos = nanoTime() - periodNanos;
                        }
                        if (!isCancelled()) {
                            //如果没有被取消,将当前任务添加到定时任务队列中
                            scheduledExecutor().scheduledTaskQueue().add(this);
                        }
                    }
                }
            }
        } catch (Throwable cause) {
            setFailureInternal(cause);
        }
    }

AbstractScheduledEventExecutor

该类的主要责任是对定时任务的执行进行行为封装,比如定时任务的定义,定时任务的调度,定时任务队列的存储。大致都比较简单,接下来我们对其API进行简单讲解。

成员变量

/**
 * 定时任务队列
 */
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue;

该变量是一个存储定时任务的队列,通过名称可以看出来是有序的,通过IDEA的快捷键查看该变量引用的位置,可以看到有取消和取两个操作。
在这里插入图片描述
既然有取的位置,那肯定得有加的位置,那任务是从哪里添加到队列里的呢?

主要方法

构造定时任务

AbstractScheduledEventExecutor对Java的ScheduledExecutorService进行了实现

/**
     * 延迟执行
     * @param command 延迟执行的任务
     * @param delay 延迟时间
     * @param unit 延迟时间单位
     */
    @Override
    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
        ObjectUtil.checkNotNull(command, "command");
        ObjectUtil.checkNotNull(unit, "unit");
        if (delay < 0) {
            delay = 0;
        }
        validateScheduled0(delay, unit);

        return schedule(new ScheduledFutureTask<Void>(
                this,
                command,
                deadlineNanos(unit.toNanos(delay))));
    }

    /**
     * 延迟执行
     * @param callable 延迟执行的任务
     * @param delay 延迟时间
     * @param unit 延迟时间单位
     */
    @Override
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
        ObjectUtil.checkNotNull(callable, "callable");
        ObjectUtil.checkNotNull(unit, "unit");
        if (delay < 0) {
            delay = 0;
        }
        validateScheduled0(delay, unit);

        return schedule(new ScheduledFutureTask<V>(this, callable, deadlineNanos(unit.toNanos(delay))));
    }

    /**
     * 在延迟initialDelay后定时执行任务
     * @param command 要执行的任务
     * @param initialDelay 初始化延迟
     * @param period 两次任务开始执行的延迟
     * @param unit 时间单位
     */
    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        ObjectUtil.checkNotNull(command, "command");
        ObjectUtil.checkNotNull(unit, "unit");
        if (initialDelay < 0) {
            throw new IllegalArgumentException(
                    String.format("initialDelay: %d (expected: >= 0)", initialDelay));
        }
        if (period <= 0) {
            throw new IllegalArgumentException(
                    String.format("period: %d (expected: > 0)", period));
        }
        validateScheduled0(initialDelay, unit);
        validateScheduled0(period, unit);

        return schedule(new ScheduledFutureTask<Void>(
                this, command, deadlineNanos(unit.toNanos(initialDelay)), unit.toNanos(period)));
    }

    /**
     * 在延迟initialDelay后定时执行任务
     * @param command 要执行的任务
     * @param initialDelay 初始化延迟
     * @param delay 下一次任务开始距前一次任务结束的时间
     * @param unit 时间单位
     * @return
     */
    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
        ObjectUtil.checkNotNull(command, "command");
        ObjectUtil.checkNotNull(unit, "unit");
        if (initialDelay < 0) {
            throw new IllegalArgumentException(
                    String.format("initialDelay: %d (expected: >= 0)", initialDelay));
        }
        if (delay <= 0) {
            throw new IllegalArgumentException(
                    String.format("delay: %d (expected: > 0)", delay));
        }

        validateScheduled0(initialDelay, unit);
        validateScheduled0(delay, unit);

        return schedule(new ScheduledFutureTask<Void>(
                this, command, deadlineNanos(unit.toNanos(initialDelay)), -unit.toNanos(delay)));
    }

其实四个方法只是一个门面,对细节进行了封装,最终都是创建了一个ScheduledFutureTask,只是构造的参数不同。

private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
    if (inEventLoop()) {
        //如果在EventLoop线程中则直接将任务添加到定时任务队列中
        scheduleFromEventLoop(task);
    } else {
        final long deadlineNanos = task.deadlineNanos();
        // task will add itself to scheduled task queue when run if not expired
        if (beforeScheduledTaskSubmitted(deadlineNanos)) {
            //添加一个异步任务
            execute(task);
        } else {
            //懒添加一个异步任务
            lazyExecute(task);
            // Second hook after scheduling to facilitate race-avoidance
            if (afterScheduledTaskSubmitted(deadlineNanos)) {
                //任务已经提交完成后
                execute(WAKEUP_TASK);
            }
        }
    }

    return task;
}

其实核心逻辑就是构造一个定时任务添加到任务队列中等待执行。


总结

今天我们讲解了Netty的定时任务机制,先介绍了Java的定时任务相关内容,后又讲解了Netty的定时任务类ScheduledFutureTask和定时任务执行器类AbstractScheduledEventExecutor,都比较简单,我们就不过多阐述了,滋滋。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

壹氿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值