时间轮-Java实现篇

简介: 在前面的文章《[时间轮-理论篇](https://developer.aliyun.com/article/910513)》讲了时间轮的一些理论知识,然后根据理论知识。我们自己来实现一个简单的时间轮。

在前面的文章《时间轮-理论篇》讲了时间轮的一些理论知识,然后根据理论知识。我们自己来实现一个简单的时间轮。

1. 理论抽象

将时间轮的理论进行抽象,主要有两个方面:

  • 时间轮的转动
  • 每一个时间间隔任务的处理,从时间间隔的Buket中取出要执行的任务,删除已经关闭的任务。将任务提交给线程池进行执行处理

2.Java实现

接口:

public interface Timer {

    void createTimerTask(TimerTask task, long delay, TimeUnit unit);

}

public interface TimerTask {

    void run();

}

实现类:

public class TimeWheel implements Timer {

    private ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private BlockingQueue<TimerTaskWrapper> addQueue = new ArrayBlockingQueue<>(128);

    //每一个tick时间间隔默认1毫秒
    private final long duration;

    //时间轮启动时间
    private volatile long startTime;

    private Thread timeWheelThread;

    private CountDownLatch startLatch = new CountDownLatch(1);

    //时间轮的tick数量
    private int tickNum = 128;

    private Bucket[] buckets;

    private boolean started = false;

    public TimeWheel(long duration, TimeUnit unit) {

        long nanos = unit.toNanos(duration);
        if (TimeUnit.MILLISECONDS.toNanos(1) > nanos) {
            this.duration = 1;
        } else {
            this.duration = unit.toMillis(duration);
        }

        this.timeWheelThread = new Thread(new Worker());
        this.buckets = new Bucket[tickNum];
        for (int i = 0; i < tickNum; ++i) {
            this.buckets[i] = new Bucket();
        }
    }

    @Override
    public void createTimerTask(TimerTask task, long delay, TimeUnit unit) {

        start();
        long deadline = System.currentTimeMillis() + unit.toMillis(delay) - startTime;
        System.out.println("deadline="+deadline);
        addQueue.offer(new TimerTaskWrapper(task, deadline));


    }

    private void start() {

        if (started) {
            return;
        }
        started = true;
        timeWheelThread.start();
        try {
            startLatch.await();
            System.out.println("启动完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //处理时间轮的转动
    class Worker implements Runnable {

        //记录tick的次数
        private long tick;

        @Override
        public void run() {

            startTime = System.currentTimeMillis();
            startLatch.countDown();
            System.out.println("Worker 启动完成..........");
            for (; ; ) {
                long time = nextTick();
                if (time <= 0) {
                    continue;
                }
                int bucketIndex = (int) (tick & (tickNum - 1));
                System.out.println("bucketIndex="+bucketIndex);
                Bucket bucket = buckets[bucketIndex];

                //处理阻塞队列中的task
                doHandleQueneTask();

                //处理过期数据
                bucket.expire(time);

                tick++;
            }


        }

        private void doHandleQueneTask() {

            for (int i = 0; i < 1024; ++i) {
                TimerTaskWrapper taskWrapper = addQueue.poll();
                //队列为空
                if (taskWrapper == null) {
                    break;
                }
                long taskTicks = taskWrapper.deadline / duration;
                taskWrapper.rounds = (taskTicks - tick) / tickNum;
                final long ticks = Math.max(taskTicks, tick);
                int bucketIndex = (int) (ticks & (tickNum - 1));
                System.out.println("inster bucketIndex = " + bucketIndex);
                Bucket bucket = buckets[bucketIndex];
                bucket.addTimerTask(taskWrapper);
            }

        }


        private long nextTick() {
            long deadline = duration * (tick + 1);
            for (; ; ) {
                long current = System.currentTimeMillis() - startTime;
                long sleepTime = deadline - current;
                if (sleepTime <= 0) {
                    return current;
                }
                try {
                    Thread.sleep(sleepTime);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class TimerTaskWrapper implements Runnable {

        private TimerTask task;

        //任务执行截止时间
        protected long deadline;

        //多少圈
        protected long rounds;

        TimerTaskWrapper prev;

        TimerTaskWrapper next;

        public TimerTaskWrapper(TimerTask task, long deadline) {
            this.task = task;
            this.deadline = deadline;
        }

        @Override
        public void run() {
            task.run();
        }

        public void expire() {
            service.execute(this);
        }
    }

    class Bucket {

        TimerTaskWrapper head;

        TimerTaskWrapper tail;


        public void addTimerTask(TimerTaskWrapper task) {

            if (task == null) {
                return;
            }

            if (head == null) {
                tail = task;
                head = tail;
            } else {
                tail.next = task;
                task.prev = tail;
                tail = task;
            }

        }

        public TimerTaskWrapper removeTimerTask(TimerTaskWrapper task) {

            TimerTaskWrapper next = task.next;

            if (task.prev != null) {
                task.prev.next = next;
            }
            if (task.next != null) {
                task.next.prev = task.prev;
            }

            if (task == head) {
                if (task == tail) {
                    head = null;
                    tail = null;
                } else {
                    head = next;
                }
            } else if (task == tail) {
                tail = task.prev;
            }
            task.prev = null;
            task.next = null;

            return next;
        }


        public void expire(long deadline) {

            TimerTaskWrapper task = head;

            while (task != null) {
                TimerTaskWrapper next = task.next;
                if (task.rounds <= 0) {
                    next = removeTimerTask(task);
                    if (task.deadline <= deadline) {
                        task.expire();
                    }
                } else {
                    //减少时间轮的圈数
                    task.rounds--;
                }
                task = next;
            }
        }

    }
}

编写一个测试案例来测试一下:

public class Test {
    public static void main(String[] args) {
        final TimeWheel wheel = new TimeWheel(1, TimeUnit.SECONDS);
        wheel.createTimerTask(new TimerTask() {
            @Override
            public void run() {
                System.out.println(1111);
                wheel.createTimerTask(this, 4, TimeUnit.SECONDS);
            }
        }, 3,TimeUnit.SECONDS);
    }
}

运行打印结果:

timewheel-test

说明:从日志的打印可以发现,在延迟三秒的情况下你会发现打印了 bucketIndex=xxx 四次。为什么会这样打印四次呢?因为当时间轮的tick在当前的时间间隔内,这个时间是不算的,从下个开始的。所以打印了四次。

3. 总结

从上面的实现可以看出来,时间间隔越长调用的就越不准确。例如刚开始的时候添加了任务到时间轮中,那么当前时间间隔就需要多消耗,实际的添加任务的执行时间为:当前时间轮剩下的时间+任务延迟执行的时间。所以如果对任务执行需要精确时间时间轮不适合。

我是蚂蚁背大象,文章对你有帮助点赞关注我,文章有不正确的地方请您斧正留言评论~谢谢!
相关文章
|
Java
Java 实现汉字按照首字母分组排序
Java 实现汉字按照首字母分组排序
681 0
|
Java 数据安全/隐私保护
JAVA 实现上传图片添加水印(详细版)(上)
JAVA 实现上传图片添加水印(详细版)
1190 0
JAVA 实现上传图片添加水印(详细版)(上)
|
网络协议 Java
Java网络编程:UDP/TCP实现实时聊天、上传图片、下载资源等
ip地址的分类: 1、ipv4、ipv6 127.0.0.1:4个字节组成,0-255,42亿;30亿都在北美,亚洲就只有4亿 2011年就用尽了。
Java网络编程:UDP/TCP实现实时聊天、上传图片、下载资源等
|
Java
Java实现拼图小游戏(7)——查看完整图片(键盘监听实例2)
由于在移动和图片中我们已经添加了键盘监听,也继承了键盘监听的接口,那么我们只需要在重写方法内输入我们的代码即可
206 0
|
存储 Java
Java实现图书管理系统
本篇文章是对目前Java专栏已有内容的一个总结练习,希望各位小主们在学习完面向对象的知识后,可以阅览本篇文章后,自己也动手实现一个这样的demo来加深总结应用已经学到知识并进行巩固。
403 0
Java实现图书管理系统
|
数据可视化 Java
Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
如果要在某一个界面里面添加功能的话,都在一个类中,会显得代码难以阅读,而且修改起来也会很困难,所以我们将游戏主界面、登录界面、以及注册界面都单独编成一个类,每一个类都继承JFrame父类,并且在类中创建方法来来实现页面
526 0
Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
|
数据可视化 Java 容器
Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现
注意由于我们计步功能的步数要在重写方法中用到,所以不能将初始化语句写在方法体内,而是要写在成员位置。在其名字的时候也要做到“见名知意”,所以我们给它起名字为step
308 0
Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现
|
Java
Java实现拼图小游戏(7)—— 作弊码和判断胜利
当我们好不容易把拼图复原了,但是一点提示也没有,完全看不出来是成功了,那么我们就需要有判断胜利的功能去弹出“成功”类的图片,以便于玩家选择是重新开始还是退出小游戏
290 0
Java实现拼图小游戏(7)—— 作弊码和判断胜利
|
Java
Java实现拼图小游戏(6)—— 移动图片(键盘监听实操练习)
当我们实现向上移动图片的时候,其实就是把空图片的下面一张图片往上移动,然后将空图片的下面那张图片设置为空图片,最后再调整初始位置为现在空图片所在位置即可,注意做完这些以后还要再加载图片,否则显示不出来
366 0
Java实现拼图小游戏(6)—— 移动图片(键盘监听实操练习)
|
存储 Java 数据库
JAVA实现网络多线程编程小游戏开发
实验总结:五子棋是一个很简单的游戏,但是如果认真对待,一个代码一个代码的去研究,会收获到很多知识,会打好学习基础。方便以后开发更高、更难的项目时打下稳固的基础。在自己开发的过程中会有各种意想不到的bug,通过查阅资料及询问老师同学进行解决对本身的一个代码能力会有一个质的增长,同时这也是一个非常快乐的过程。有进步,总归是好事。
JAVA实现网络多线程编程小游戏开发