前言
在看这篇文章的时候对其中超时控制一块儿有点好奇。通过时间轮来控制超时?啥是时间轮?怎么控制的?文章会先介绍常见的计时超时处理,再引入时间轮介绍及 netty 在实现时的一些细节,最后总结下实现的一些优缺点。个人观点,如有错误望指正。
计时/超时
JDK 中有许多经典的计时/超时计算的实现。例如 AQS 中的 doAcquireNanos
, FutureTask 中的 awitDone
, 从原理上来讲都是通过以下这种两种计时方式来实现的。
这里有两个问题:
为什么要用 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 其实是自旋的一种优化,避免长时间空转。
unpark 调用
其他线程调用了休眠线程的 interrupt,但不会抛出
InterruptedException
虚调用(这个不太了解。。)
为什么要用 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