相关文章推荐

时间轮是一个环状的存储结构,它像一个时钟,我们可以给环设置指定数量的刻度,比如一个环60个刻度或者60*60刻度,存任务时根据index取到数组里,那么在取任务时可以根据时间戳来计算Index, 从而实现轮询时间轮里的任务。

如下用数组作为存储任务的时间轮,也可以使用链表替代。

Java中的列表底层结构是数组,可用Set或List,  下面以数组为存储单元详解实现思路。

一、实现思路

1. 存任务

以ID做为计算时间轮中的index位置,假如时间轮的槽位有60个,可设置index=ID%60, 如果能拿到值,并不为空,那么存任务时,将当前任务添加到列表后。

为了避免空指针异常,可以在new的时候给所有槽位初始化空数组。

2. 取任务

由于我们是以时间的刻度作为存储的index, 那么可以直接使用当前时间戳计算出对应的环上的id。

index= timestamp/1000 %60

即每次pull前,需要计算一下index,相当于是秒表的转动,假如pull线程每隔1s休眠一次,那么在可以实现每秒从时间轮中取出一组任务,60s 一圈,每s都会去时间轮里拿任务。

二、代码实现

1. 抽象类TimeWheel

package com.bing.sh.timewheel;
import java.util.*;
public abstract class TimeWheel {
    // 每个槽位对应一个时间刻度
    protected int bufferSize = 60;
    public TimeWheel() {
    public TimeWheel(int size) {
        this.bufferSize = size;
    protected abstract void pushRingData(int ringId, Object obj);
    protected abstract Collection<?> removeRingData(int ringId);
    public void pusData(int ringId, Object obj) {
        int index = countIndex(ringId);
        this.pushRingData(index, obj);
    public Collection<?> removeData(int ringId) {
        int index = countIndex(ringId);
        return this.removeRingData(index);
    public int countIndex(int ringId) {
        if (ringId < 0) {
            ringId = -ringId;
        return ringId % bufferSize;

        使用ringId作为key来计算index, 存取前都要计算一遍 ,pushData用来存任务,removeData用来从时间轮里取任务。

2. 用数组实现

        创建EntryWheel实现类,继承TimeWheel, 可使用initBuffer()方法给时间轮初始化, 可用ReentrantReadWriteLock 加读写锁,也可不加,因为轮询的时候是单线程的取。

package com.bing.sh.timewheel;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class EntryWheel extends TimeWheel {
     * 采用数组的形式, 通过index来作为存储的key, value为一个List或者Set列表
    private Object[] ringBuffer;
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public EntryWheel() {
        super();
        initBuffer();
    public void initBuffer() {
        ringBuffer = new Object[bufferSize];
        for (int i = 0; i < bufferSize; i++) {
            ringBuffer[i] = new HashSet<>();
    public EntryWheel(int size) {
        super(size);
        initBuffer();
    @Override
    protected void pushRingData(int index, Object obj) {
        // 1. 根据ringId计算index
        // 2. 根据index存放obj
        try {
            lock.writeLock().lock();
            Set<Object> objects = (Set<Object>) ringBuffer[index];
            objects.add(obj);
            ringBuffer[index] = objects;
        } finally {
            lock.writeLock().unlock();
    @Override
    protected Collection<?> removeRingData(int index) {
        try {
            lock.readLock().lock();
            Set<Object> objects = (Set<Object>) ringBuffer[index];
            ringBuffer[index] = new HashSet<>();
            return objects;
        } finally {
            lock.readLock().unlock();

3. 用Map实现

        用Map替换为数组。

package com.bing.sh.timewheel;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class MapWheel extends TimeWheel {
    private Map<Integer/** id**/, Set<Object>> ringDataMap;
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public MapWheel() {
        ringDataMap = new ConcurrentHashMap<>(bufferSize);
        initMap();
    private void initMap() {
        for (int i = 0; i < bufferSize; i++) {
            Set<Object> objectSet = new HashSet<>();
            ringDataMap.put(i, objectSet);
    public MapWheel(int size) {
        super(size);
        ringDataMap = new ConcurrentHashMap<>(bufferSize);
        initMap();
    @Override
    protected void pushRingData(int ringId, Object obj) {
        try {
            lock.writeLock().lock();
            Set<Object> objects = ringDataMap.get(ringId);
            objects.add(obj);
        } finally {
            lock.writeLock().unlock();
    @Override
    protected Collection<?> removeRingData(int ringId) {
        try {
            lock.readLock().lock();
            Set<Object> objects = ringDataMap.get(ringId);
            ringDataMap.put(ringId, new HashSet<>());
            return objects;
        } finally {
            lock.readLock().unlock();

4. 测试数组

package com.bing.sh.timewheel;
import org.junit.Before;
import org.junit.Test;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class EntryWheelTests {
    Random random;
    @Before
    public void setup() {
        random = new Random();
    @Test
    public void testEntryWheel() {
        EntryWheel entryWheel = new EntryWheel();
        new Thread(() -> {
            while (true) {
                entryWheel.pusData(random.nextInt(60), random.nextInt(1000000));
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
        }).start();
        new Thread(() -> {
            while (true) {
                int nowTime = (int) (System.currentTimeMillis() / 1000 % 60);
                Set<Object> objectSet = (Set<Object>) entryWheel.removeData(nowTime);
                System.out.println(nowTime + ">>>" + objectSet);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
        }).start();
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();

打印结果:

每秒读任务, 直到打印10次结束

5. 测试Map 

        用同样的方式测试Map实现的时间轮。

        可以发现,也能够实现按s读取, 每次读取到数组的大小是随机的。

6. 小结 

        1) 时间轮的size可根据实际开发需求设置,这里为了好理解,设置成60。

        2) 每次存取前可根据size和ringId 取模,或者采用其他算法也可以,只要散布在轮子上的数据均匀即可。

基于Vue+SpringCloud博客的设计实现---微服务基础版本组件1.0版本 博客采用Vue+SpringCloud前后分离的方式。博客采用了高可用Eureka(可以替换成其他微服务组件)以及高可用Zuul,使用以Es搜索引擎作为Zpkin的存储方式去跟踪定位博客的微服务的Api指标,微服务之间负载均衡使用Feign接口,整个项目均写了回退不会发生级联效应。 项目的亮点 所有互联网常用的代表中间件均涉及使用,基本是一个完整的全栈项目,整个博客用的是微服务架构设计与分布式部署方式,整体代码均有注释,并且扩展方便,最终部署的方式需要采用Docker方式。 博客的功能介绍 用户的个人中心:用户的登录与注册的Token验证,前后拦截器拦截Token。拼图,阿里云智能验证,动态加载JS,控制Token也可以在Zuul路由上操作。 用户安全中心:SMTP邮箱注册邮箱,阿里云短信API注册手机以及其他个人安全信息和调用安全认证服务的接口,安全完成度最全。 用户文件头像上传中心:博客所用到的所有的图片和用户的图片均用阿里云OSS文件服务器,外网url,也可以采用本地机器存储。 用户签到中心:持续签到和累计签到奖励机制,以及会员导致经验值增益不同的机制,博客每日任务,排名特权,基本按照贴吧写的。 用户会员中心:SVIP与VIP,定时任务/RabbitMQ延迟队列/登录验证三种判定会员截止时间到期用邮箱去提醒 用户支付中心:我的钱包和支付宝支付以及打印我的账单,内网穿透获得异步通知作为结果判定标志,原始支付的普通会员,二维码支付的超级会员,账单分页,Csv定制,消费图,优惠券,基于RabbitMQ/Redis两种实现的延迟队列 用户博客中心:发布,更新,删除,评论,点赞,收藏,转发,排行榜已经完成。博客中心是博客的核心,分页和轮滑加载均实现,用Redisson来实现分布式锁控制文章 搜索引擎中心:文章提示信息的增删改查,分页,高亮模糊排序查询 用户的消息中心:websocket聊天与用户的所有个人消息 用户的个人空间:这个会涉及到个人博客空间与博客好友,博客云会控制上传和下载文件,会员会有速度特权,类似百度云,后续会完成上传。 一、简介1、循环缓冲区的实现原理环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写入。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以保证数据的正确性。如果有多个读写用户访问环形缓冲区,那么必须...   由于工作的需要,得实现一个用于控制事件超时抛弃的时间轮,由于这是一个相对独立的接口,就总结分享一下。   首先看下需求,此时间轮需要具备下面几个功能:   1)能添加事件,同时附上其超时时间;   2)如果事件正常执行结束,可以显示将其从时间轮上剔除掉,而不需要等时间轮自动移除;   3)如果事件到了设定的超时时间还没执行完,则时间轮需将其剔除掉,并发送一个超时的消息... 改进的单时间轮其实是一个对时间和空间折中的思路,即不会像单时间轮那样有O(1)的时间复杂度,但也不会像单时间轮那样对bucket个数有巨大的需求。在 Linux 系统中,我们可以设置slot为1个jiffy(1/HZ)的定时器,假设最大的到期时间范围要达到 2^32个 jiffies,如果采用上面这样的单时间轮,我们就需要2^32个 bucket,这会带来巨大的内存消耗,显然是需要优化改进的。相比简单的单时间轮,时间上仅仅多了1/256次(为约等于值,忽略了tv2以上产生的进位操作)的链表迁移操作耗时。 把需要执行的定时任务的具体时间放在时钟对应的刻度,时钟指针每指一个刻度代表该时间点的定时任务全部需要执行,然后全部提交到线程池异步执行。如果定时任务的时间范围跨度大,可以将时间轮修改为周期时间轮或者分层时间轮。这里实现时间轮是周期时间轮,时间刻度为秒。 Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visual C++源代码 130 如何使用存储过程查询时间段数据Visua 时间轮算法是一种高效、精确、可扩展的定时任务调度算法。它可以将任务按照时间戳放入到对应的槽位中,然后定时扫描每个槽位,执行其中的任务。时间轮算法还支持多级时间轮,可以将时间轮的刻度细分到更小的时间间隔,提高定时任务的精确度。由于时间轮算法具备高效的定时任务调度、支持动态调整、支持高并发和可扩展性强等优点,因此被广泛应用于各种不同的定时任务场景。在实际使用时间轮算法时,还需要注意一些细节。例如,在多级时间轮中,每个子时间轮的大小应该根据实际需求进行设置,以便充分利用时间轮的范围和精度。 时间轮算法是一种比较常见的时间计数器算法,它主要用于定时器的处理。将一个任务添加到时间轮中,实际上就是将该任务放置到某个槽位中。在时间轮滚动时,我们首先获取当前的槽位,然后依次检查该槽位中所有任务是否到期。如果到期了,则执行该任务,并将该任务从任务列表中移除。我们根据时间间隔和槽数量计算出任务所在的槽位,并将该任务加入到相应的槽位中。总之,基于时间轮算法的定时器实现方法非常高效且精准,并且代码量也相对较少。具体来说,就是让时间轮不断地滚动,检查每个槽位中是否有到期的任务,如果有,则执行该任务。     关于TimingWheel(时间轮)算法的任务定时器网上有很多文章,但是却找不到基于java成系统的文章,所以今天把我在公司做的且稳定运行半年多的TimingWheel系统分享给大家。 1 TimingWheel基本原理:     众所周知寻常的定时器大概有两种,一种是开阻塞线程,另一种是开一个任务队列然后定期扫描。显而易见这两种方式的弊端很明显,前者对线程消耗过大,后者对时间消耗过大... 问题引入:游戏里面每个Player身上有很多buffs,在每一个tick(最小时间段)都要去检查buff里面的每一个buff是不是过期,产生的效果如何,造成在每个tick里面都去遍历一个长list,明显很不好。 怎么优化? 1.原始模型: buff的状态在每一个tick里面都要更新!可以想象指针每移动一下,都会非常沉重地拖着所有的BuffList,好可怕…… 2. 优化模型1: 我们...
 
推荐文章