定时器实现之时间轮算法

前言

在看这篇文章的时候对其中超时控制一块儿有点好奇。通过时间轮来控制超时?啥是时间轮?怎么控制的?文章会先介绍常见的计时超时处理,再引入时间轮介绍及 netty 在实现时的一些细节,最后总结下实现的一些优缺点。个人观点,如有错误望指正。

计时/超时

JDK 中有许多经典的计时/超时计算的实现。例如 AQS 中的 doAcquireNanosFutureTask 中的 awitDone, 从原理上来讲都是通过以下这种两种计时方式来实现的。

这里有两个问题:

  1. 为什么要用 LockSupport.parkNanos 不用 Object.wait/Thread.sleep?

    LockSupport 是使用来实现底层操作,比如 park/unpark,源码文档开头第一句是 LockSupport 是创建锁或其他同步类的基本线程同步原语(p.s. 个人觉得就是基本工具的意思吧)。根据文档内容我整理了几个要点:

    有三种情况会从休眠中唤醒:

    回到问题,为什么不使用 Object.wait ?个人认为是因为我们不能保证 wait 是在 notify/notifyAll 之前执行的。如果在之后,就会一直阻塞下去。

    为什么不用 Thread.sleep?个人认为首先需要要处理 InterruptedException,其次 sleep 必须休眠设定的时间,无法中途唤醒。

  • 使用 LockSupport 需要每个线程关联一个 permit,类似于 Semaphore 信号量同步类计数的原理,只是不会像 Semaphore 一样累加,类似于只有 0/1 两个值。调用 park 阻塞 permit 为 0,调用 unpark 恢复 permit 为 1。

  • 由于 permit 的原因,线程之间的竞争具有活性(liveness),非 0 即 1,不会产生死锁。

  • no reason return:只要 unpark 就会在任何时候恢复,所以一般建议在循环中使用,时刻检查循环条件,所以 park 其实是自旋的一种优化,避免长时间空转。

  1. unpark 调用

  2. 其他线程调用了休眠线程的 interrupt,但不会抛出 InterruptedException

  3. 虚调用(这个不太了解。。)

  • 为什么要用 System.nanoTime 不用 System.currentTimeMillis?

    currentTimeMillis 返回的是当前时间和 1970.01.01 midnight 之间的差值。如果发生时钟回拨或者手动把时钟改到以前,两次记录的时间差值就有可能为负了。

    nanoTime 在 JDK 文档中是建议用来做耗时计算的。nanoTime 并不是严格意义上的时间,只是 JVM 实例启动后随机选取的一个固定且任意的原点时间(可能是未来时间,值有可能为负数)开始计时。所以正确使用 nanoTime 的姿势是:

    // 耗时计算
    long startTime = System.nanoTime();
    long estimatedTime = System.nanoTime() - startTime;
    
    // 比较两个时间
    long t0 = System.nanoTime();
    long t1 = System.nanoTime();
    // 由于存在溢出的问题
    // t0 < t1 不一定成立,比如 t0 是正数,t1 溢出成负数了
    // 所以应该使用 t1 - t0 < 0
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值