不要过分相信自己的直觉之 RateLimiter.getRate()
最近使用 Guava 中 RateLimiter.getRate() 方法时遇到了一个反直觉的现象,这里贴出来给大家分享一下。
现象
我们先看下下面这段代码
1 | public static void main(String[] args) { |
直觉上最终会输出 10.0
,实际最终也是输出了 10.0
。
可是如果我们将上述代码中的 10.0D
变为 15.0D
,结果会输出什么呢?
1 | public static void main(String[] args) { |
执行后输出的值竟然是 14.999999999999998
。看到这个结果我觉得非常的奇怪,毕竟直觉告诉我应该输出 15.0
才对。
刨根问底
看到这样的现象,我们首先就要从源码上分析一下,看一下 Guava 内部是如何处理的。
打开 RateLimiter.getRate() 方法,发现其返回的是抽象方法 doGetRate() 的值。
1 | public final double getRate() { |
我们创建 RateLimiter 的实例时默认创建的是类型为 SmoothBursty 的对象,该类的 doGetRate() 方法实现如下:
1 |
|
这里竟然不是直接返回我们创建 RataLimiter 时设置的值,而是通过除法运算得来的。
继续分析 stableIntervalMicros 是怎么设值的。
1 | /** |
至此结论已经非常清晰了:由于在创建 RateLimiter 时成员变量 stableIntervalMicros 便是通过简单的除法运算得来的,当 1000000 微妙(1 秒)不能够被传入的限流值除尽时 stableIntervalMicros 就已经丢失了精度,从而导致在这种情况下调用 doGetRate() 是不能够得到我们创建 RateLimiter 时传入的原始值的。
发散
Guava 为什么不在创建 RateLimiter 时把我们传入的原始值保存在一个成员变量里,调用 doGetRate() 方法时直接该成员变量的值呢?
我个人的理解可以从变量和方法命名上去分析,RateLimiter.create(double permitsPerSecond) 方法的变量名 permitsPerSecond 其含义是每秒产生多少令牌。而 RateLimiter.getRate() 从方法名以及 Guava 的注释上来看该方法的含义更倾向于提供产生令牌的实际速率。
总结
在使用一些三方的库甚至是 Java 本身提供的一些方法时要弄清楚其使用场景,尽量多验证一下各种边界值、特殊值的情况,不能盲目相信自己的直觉。