时间轮实现延迟队列

什么是时间轮

时间轮出自Netty中的HashedWheelTimer,是一个环形结构,可以用时钟来类比,钟面上有很多bucket,每一个bucket上可以存放多个任务,使用一个List保存该时刻到期的所有任务,同时一个指针随着时间流逝一格一格转动,并执行对应bucket上所有到期的任务。任务通过取模决定应该放入哪个bucket。和HashMap的原理类似,newTask对应put,使用List来解决 Hash 冲突。
时间轮

时间轮怎么实现延迟队列

//时间轮对象中申明一个存放时间轮对象
private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
  • private ,是为了遵循开闭原则,防止外部直接操作时间轮
  • volatile,是为了多线程可见性,防止工作内存未刷新主内存的值,导致出现数据不一致,关键字虽然可以保证部分有序性(具体请查阅相关资料),但实际代码操作中并没有使用。
  • static,为了保证是多个实例使用一个类成员变量
  • Map<Integer, List> 使用map结构是为了组装key-value(时间刻度-List<任务>)
    最后需要使用并发map,是为了保证多线程操作不会出现线程问题

补充一点并发三要素

  1. 原子性:所有操作必须为原子的,天然原子操作就是赋值与四则运算,其中32位系统的除法运算不为原子操作
  2. 有序性:因为编译器会对代码进行优化,代码的顺序会出现变化,在单线程的情况下,对结果无影响。在多线程情况下如果出现对成员变量操作,且代码执行顺序对中间态有影响的
  3. 可见性:cpu操作的不是直接内存,而且有一个工作内存,也就是cpu的高速缓存(我们称之为工作内存),每个cpu的核都有一个工作内存
多核执行线程,涉及到cpu高速缓存问题。
cpu  <——> 高速缓存(工作内存)  <———>  主内存(物理内存)

1.启动线程,从物理内存读取一份到高速缓存中,此时stop是false,!stop为true,并循环执行
2.接下来执行,main线程从物理内存读取stop后更改成true,但此时stop并不保证写入物理内存的时效,此时另外一个线程的stop取的一直是false。因此会在这里导致循环不能退出,这里是一个典型的并发可见性问题。可以用volatile关键字修饰变量,保证修改后,强制写入物理内存,保证对另外一个线程的可见(这里是使用的cpu缓存一致算法实现的)

存放数据到时间轮

 public void pushTimeRing(int ringSecond, int jobId){
        // push async ring
        List<Integer> ringItemData = ringData.get(ringSecond);
        if (ringItemData == null) {
            ringItemData = new ArrayList<Integer>();
            ringData.put(ringSecond, ringItemData);
        }
        ringItemData.add(jobId);

        logger.debug(">>>>>>>>>>> xxl-job, schedule push time-ring : " + ringSecond + " = " + Arrays.asList(ringItemData) );
    }

存放刻度,与任务id,这里的刻度是秒,如果刻度不够用怎么办?最快的方法是加时间刻度,处理但是这么做,内存增加,利用率低,无用功做的比较多。最佳的办法是,加上一个圈数round,如下:

private volatile static Map<Integer, List<Map<Integer,Integer>>> ringData = new ConcurrentHashMap<>();

  • List<Map<Integer,Integer>> 其中Map是存放当前刻度的对象,不管是第几圈的,Map的key是为了存放圈数round,value存放任务id等。

每次到当前刻度后遍历list,当round=0的时候执行任务后移除,如果任务不为0,则round=round-1;

刻度移动

在xxl-job中,处理如下:


            try {
                    TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
                } catch (InterruptedException e) {
                    if (!scheduleThreadToStop) {
                        logger.error(e.getMessage(), e);
                    }
                }
  • 5000 这个值是xxl-job的刻度,每次走5000,System.currentTimeMillis()%1000 取当前毫秒数,做差然后休眠,就是为了把时间轮对准每次5s的刻度

每次执行一次,然后就会校准刻度,中间是通过while循环实现的推动时间轮的

TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);

这样就可以完成了延迟任务队列了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值