Netty下的时间轮核心源码解读

1、创建时间轮

public HashedWheelTimer(
            ThreadFactory threadFactory,
            long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection,
            long maxPendingTimeouts, Executor taskExecutor) {
        // taskExecutor默认值为调用方执行任务实现(没有池化)
        checkNotNull(threadFactory, "threadFactory");
        checkNotNull(unit, "unit");
        checkPositive(tickDuration, "tickDuration");
        checkPositive(ticksPerWheel, "ticksPerWheel");
        this.taskExecutor = checkNotNull(taskExecutor, "taskExecutor");

        // Normalize ticksPerWheel to power of two and initialize the wheel.
        wheel = createWheel(ticksPerWheel);
        mask = wheel.length - 1;

        // Convert tickDuration to nanos.
        long duration = unit.toNanos(tickDuration);

        // Prevent overflow.
        if (duration >= Long.MAX_VALUE / wheel.length) {
            throw new IllegalArgumentException(String.format(
                    "tickDuration: %d (expected: 0 < tickDuration in nanos < %d",
                    tickDuration, Long.MAX_VALUE / wheel.length));
        }

        if (duration < MILLISECOND_NANOS) {
            logger.warn("Configured tickDuration {} smaller than {}, using 1ms.",
                        tickDuration, MILLISECOND_NANOS);
            this.tickDuration = MILLISECOND_NANOS;
        } else {
            this.tickDuration = duration;
        }

        workerThread = threadFactory.newThread(worker);

        leak = leakDetection || !workerThread.isDaemon() ? leakDetector.track(this) : null;

        this.maxPendingTimeouts = maxPendingTimeouts;
		// 限制不能创建过多的时间轮实例,最好只创建一个实例
        if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&
            WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {
            reportTooManyInstances();
        }
    }

2、启动时间轮

// HashedWheelTimer的start方法最终调用workerThread.start();且只会执行一次

// 该类为workerThread参数的Runnable实现类
private final class Worker implements Runnable {
        private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
		// 已经经过的时钟数
        private long tick;
        @Override
        public void run() {
            // (开始时间/基准时间,后续的相关计算操作都要参照该时间来计算)
            startTime = System.nanoTime();
            if (startTime == 0) {
                // We use 0 as an indicator for the uninitialized value here, 
                // so make sure it's not 0 when initialized.
                startTime = 1;
            }

            // Notify the other threads waiting for the initialization at start().
            startTimeInitialized.countDown();

            do {
                // 重要解读方法一
                final long deadline = waitForNextTick();
                if (deadline > 0) {
                	// 得到当前对应的桶下标
                    int idx = (int) (tick & mask);
                    // 移除所有取消的任务
                    processCancelledTasks();
                    HashedWheelBucket bucket =
                            wheel[idx];
                    // 重要解读法三(将任务存入对应的桶中)
                    transferTimeoutsToBuckets();
                    // 重要解读方法二
                    bucket.expireTimeouts(deadline);
                    // 时钟次数+1
                    tick++;
                }
            } while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
    // 省略其他代码.....
}

// 解读方法一
private long waitForNextTick() {
			// 本轮tick的截止时长值
            long deadline = tickDuration * (tick + 1);
			// 直到当前的时间和开始时间的差值大于等于tick时退出
            for (;;) {
                final long currentTime = System.nanoTime() - startTime;
                long sleepTimeMs = (deadline - currentTime + 999999) / 1000000;
                // 转换成下面的写法方便理解(本轮tick的截止时长值加上开始时间和当前时间的关系)
                // 小于等于则说明无需等待,则直接返回当前时间与开始时间的差值,大于则说明是未来的时刻则需要使用Thread.sleep进行等待(999999是为了转换纳秒到毫秒)
				// long sleepTimeMs = (deadline - System.nanoTime() + startTime + 999999) / 1000000;
                if (sleepTimeMs <= 0) {
                    if (currentTime == Long.MIN_VALUE) {
                        return -Long.MAX_VALUE;
                    } else {
                        return currentTime;
                    }
                }

                // Check if we run on windows, as if thats the case we will need
                // to round the sleepTime as workaround for a bug that only affect
                // the JVM if it runs on windows.
                //
                // See https://github.com/netty/netty/issues/356
                if (PlatformDependent.isWindows()) {
                    sleepTimeMs = sleepTimeMs / 10 * 10;
                    if (sleepTimeMs == 0) {
                        sleepTimeMs = 1;
                    }
                }

                try {
                    Thread.sleep(sleepTimeMs);
                } catch (InterruptedException ignored) {
                    if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) {
                        return Long.MIN_VALUE;
                    }
                }
            }
        }

// 解读方法二
private void transferTimeoutsToBuckets() {
            // transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just
            // adds new timeouts in a loop.
            // 一次最多执行10w次poll操作(原因见英文说明)
            for (int i = 0; i < 100000; i++) {
                HashedWheelTimeout timeout = timeouts.poll();
                if (timeout == null) {
                    // all processed
                    break;
                }
                if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) {
                    // Was cancelled in the meantime.
                    continue;
                }
				// 每个任务的初始值
				// 根据截止时间算出时钟次数
                long calculated = timeout.deadline / tickDuration;
                // 根据时钟次数和已经经过的时钟次数的差值除以轮的大小得到剩余的轮次
                timeout.remainingRounds = (calculated - tick) / wheel.length;

                final long ticks = Math.max(calculated, tick); // Ensure we don't schedule for past.
                // 得到该任务的下标
                int stopIndex = (int) (ticks & mask);

                HashedWheelBucket bucket = wheel[stopIndex];
                // 添加到对应的双向链表尾部
                bucket.addTimeout(timeout);
            }
        }


// 解读方法三(调用任务执行的方法),deadline表示执行任务的deadline值小于等于该值的任务
public void expireTimeouts(long deadline) {
            HashedWheelTimeout timeout = head;

            // process all timeouts
            while (timeout != null) {
                HashedWheelTimeout next = timeout.next;
                if (timeout.remainingRounds <= 0) {
                	// 先移除任务
                    next = remove(timeout);
                    if (timeout.deadline <= deadline) {
                    	// 执行任务(最终通过配置的Executor来执行任务)
                        timeout.expire();
                    } else {
                        // The timeout was placed into a wrong slot. This should never happen.
                        throw new IllegalStateException(String.format(
                                "timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
                    }
                } else if (timeout.isCancelled()) {
                	// 移除已经取消的任务
                    next = remove(timeout);
                } else {
                	// 减少任务的轮次
                    timeout.remainingRounds --;
                }
                timeout = next;
            }
        }


3、添加延迟任务

源码分析

@Override
    public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
        checkNotNull(task, "task");
        checkNotNull(unit, "unit");

        long pendingTimeoutsCount = pendingTimeouts.incrementAndGet();

        if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) {
            pendingTimeouts.decrementAndGet();
            throw new RejectedExecutionException("Number of pending timeouts ("
                + pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending "
                + "timeouts (" + maxPendingTimeouts + ")");
        }
		// 可以不用收到调用时间轮的start()
        start();

        // Add the timeout to the timeout queue which will be processed on the next tick.
        // During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket.
        long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;

        // Guard against overflow.
        if (delay > 0 && deadline < 0) {
            deadline = Long.MAX_VALUE;
        }
        HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);
        timeouts.add(timeout);
        return timeout;
    }

使用例子:

HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS);
        timer.newTimeout(new TimerTask() {
            /**
             *
             * @param timeout HashedWheelTimeout类型
             * @throws Exception
             */
            @Override
            public void run(Timeout timeout) throws Exception {
                try {
                    System.out.println("业务代码执行部分");
                } catch (Exception e) {
                    System.err.println(e.getMessage());
                }
            }
        }, 10, TimeUnit.SECONDS);

4、实现定时任务的执行

timer.newTimeout(new TimerTask() {
            /**
             *
             * @param timeout HashedWheelTimeout类型
             * @throws Exception
             */
            @Override
            public void run(Timeout timeout) throws Exception {
                try {
                    System.out.println("业务代码部分");
                    // 再次调用newTimeOut,保证每次执行完任务后,再添加新一轮的任务
                    timer.newTimeout(this, 1, TimeUnit.SECONDS);
                } catch (Exception e) {
                    System.err.println(e.getMessage());
                }
            }
        }, 10, TimeUnit.SECONDS);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

长了脚の妖怪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值