• 游戏服务器中定时任务的实现


    工作中研究过Java技术栈下几种常见的定时器实现,在此做个总结:

    1. JDK提供的TimerTask

    在游戏服务器中几乎不会使用它,因为有明显的缺点:

    首先,它是单线程执行的,如果某个任务执行太长,可能会影响后续任务的准时执行。

    其次,游戏中经常有修改系统时间测活动的需求,这种场景不适合。因为它底层是在schedule task的时候就算好了wait的时间,无法在系统时间修改后自动调整。

    2. 循环 + PriorityQueue

    最主流的实现方式,也是Netty等框架实现定时任务的方式。

    基本思路是:开单独线程不断循环,每次sleep固定时间片,然后依次遍历任务队列中的任务,若到时间了就扔到业务线程池执行。任务维护在PriorityQueue的队列中,这种数据结构好处是插入的时间复杂度仅为O(logN),好于链表的O(N)。

    因为不存在长时间阻塞,所以可以做到系统时间修改后任务时间自动调整。

    3. 时间轮

    适合于时间精度要求不高的场合,比如IO超时处理。

    精度不高的原因是时间精度依赖于轮中的格子数,但如果格子数越多,空间消耗也会越大。本质上这是一个时间-空间trade off的问题。

    时间轮在Netty、Kafka、Zookeeper等很多Java开源项目中都有广泛应用。仿照实现一个并不难。
    因为需要一格格计算,所以无法做到系统时间修改后任务时间自动调整。

    4. quartz

    这种方式底层实现也是让线程sleep或wait,类似于方式1和2。

    不过quartz本身提供了更高阶的功能:如crontab表达式,可以指定日期(如每日零点)定时执行;也能支持更复杂的触发逻辑,如延迟3秒后每1秒执行一次;还提供任务持久化功能,即使宕机后任务也能恢复。

    quartz本质上是封装好的更重量级的任务调度方案,类似的还有xxl-job,后者还提供分布式和可视化监控的功能。不过对于游戏来说,quartz一般也够用了。

    quartz可以做到系统时间修改后任务时间自动调整。通过quartz源码可以看到,调度线程每次wait不是直接到下个任务执行时间,而是30秒左右的随机值,这样反复wait直到任务执行的时间点。因此修改系统时间后,调度线程在当前wait时间结束后,能将任务执行时间重新调整。

    long now = System.currentTimeMillis();
    // 默认30秒加随机浮动
    long waitTime = now + getRandomizedIdleWaitTime(); 
    long timeUntilContinue = waitTime - now;
    synchronized(sigLock) {
        try {
            if(!halted.get()) {
                  // QTZ-336 A job might have been completed in the mean time and we might have
                  // missed the scheduled changed signal by not waiting for the notify() yet
                  // Check that before waiting for too long in case this very job needs to be
                  // scheduled very soon
                  if (!isScheduleChanged()) {
                      // 在主循环中反复执行wait,直到任务执行
                      sigLock.wait(timeUntilContinue); 
                  }
            }
        } catch (InterruptedException ignore) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    要注意的是,往后调系统时间没有问题,但如果是往回调时间,那么在调整前已过期的任务不会重新执行。例如,用crontab设置了每日0点执行,如果从某天1点调整到前一天23点,那么在经过0点时任务不会重新执行。

  • 相关阅读:
    Spring中Bean的生命周期详解
    【Push Kit】关于推送消息没有锁屏通知的问题
    【MM小贴士】物料主数据的中止与后继(3)
    【opencv】多版本安装
    查看apk签名
    【LeetCode】622. 设计循环队列
    开了挂似的,直接涨薪25K,跪谢这份Java性能调优实战宝典
    Windows OpenGL ES 图像褐色
    c++ 中 auto, auto & 和 const auto & 的区别
    Windows上启用NTP服务器功能
  • 原文地址:https://blog.csdn.net/needmorecode/article/details/128074238