Google Guava之RateLimiter核心源码解读(上)

RateLimiter是Google Guava框架的一个限速器,通常用于控制对某个资源的访问速率。

限速常见的有两种实现方式,一种是令牌桶,另一种是漏桶。

RateLimiter选择了令牌桶作为其底层实现,按照固定速率投放令牌,同时支持突发流量。

本篇先来解读一下Ticker和Stopwatch,它们是RateLimiter底层时间计算的基础。

代码基于Guava 23.0版本。

Ticker

Ticker的意思钟表,里面只有一个read()方法,用来获取当前时间。

核心代码解读:

public abstract class Ticker {
  // 获取当前时间
  public abstract long read();

  // Ticker默认实现. read()内部实际是调用java.lang.System.nanoTime()方法
  private static final Ticker SYSTEM_TICKER =
      new Ticker() {
        @Override
        public long read() {
          return Platform.systemNanoTime();
        }
      };
}

Stopwatch

Stopwatch是一个计时器,也叫秒表。通常用于测量程序功能的运行时长。

功能和实际的物理秒表非常相似,现在手机一般都会有秒表功能,大家可以先用手机感受一下,然后再去理解该类就会比较简单了。

秒表通常会支持开始、停止和重置。可以将多次开始和结束之间的时间进行累计,支持重置秒表后重新开始。没错,Stopwatch就实现了这个最基本的功能。

核心代码解读:

public final class Stopwatch {
  /**
   * 钟表. 主要用于获取当前的纳秒数,默认使用System.nanoTime().
   */
  private final Ticker ticker;

  /**
   * 秒表的运行状态.<br>
   * 调用start()时设置为true,表示运行中,开始计时.<br>
   * 调用stop()、reset()时设置为false,表示停止,结束本次计时.
   */
  private boolean isRunning;

  /**
   * 秒表总的计时时长.<br>
   * 计算规则:每执行一次"start() 到 stop()"之间经过的时间之和。从"stop() 到 start()"之间的时间不做累计.<br>
   * 更新时机:仅在执行stop()和reset()时更新.
   */
  private long elapsedNanos;

  /**
   * 秒表开始计时的时间. 每次调用start()时更新为当前时间,进行下一轮的计时.
   */
  private long startTick;

  /**
   * 默认构造函数. 使用默认的Ticker
   */
  Stopwatch() {
    this.ticker = Ticker.systemTicker();
  }

  /**
   * 支持自定义Ticker的构造函数. 例如对于Android,Ticker可以基于android.os.SystemClock.elapsedRealtimeNanos()来实现.
   *  
   * @param ticker
   */
  Stopwatch(Ticker ticker) {
    this.ticker = checkNotNull(ticker, "ticker");
  }

  /**
   * 启动秒表
   */
  @CanIgnoreReturnValue
  public Stopwatch start() {
    checkState(!isRunning, "This stopwatch is already running.");
    isRunning = true; // 状态设置为运行中
    startTick = ticker.read(); // 保存计时开始时间
    return this;
  }

  /**
   * 停止秒表
   */
  @CanIgnoreReturnValue
  public Stopwatch stop() {
    long tick = ticker.read(); // 获取当前时间
    checkState(isRunning, "This stopwatch is already stopped.");
    isRunning = false; // 状态设置为停止运行
    elapsedNanos += tick - startTick; // 将本次"start() 到 stop()"之间的计时时长累计到elapsedNanos中
    return this;
  }

  /**
   * 获取秒表总的计时时长. 实际上,就是返回截至当前时间为止的elapsedNanos.<br>
   * 如果秒表还在计时中,elapsedNanos需要累计"本次计时到当前时间为止"的耗时,因此返回:ticker.read() - startTick + elapsedNanos.<br>
   * 如果秒表已经停止了,那么直接返回elapsedNanos。因为如果已经调用了stop(),则已经执行了一次:elapsedNanos += ticker.read() - startTick.
   *  
   * @return
   */
  private long elapsedNanos() {
    return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
  }

  /**
   * 重置秒表
   */
  @CanIgnoreReturnValue
  public Stopwatch reset() {
    elapsedNanos = 0; // 计时清0
    isRunning = false; // 设置状态为停止
    return this;
  }
}

示例代码

最后,再提供一个例子方便大家测试。

import java.util.concurrent.TimeUnit;

import org.junit.Test;

import com.google.common.base.Stopwatch;

public class StopwatchTest {

    @Test
    public void testStopwatch() {
        Stopwatch watch = Stopwatch.createStarted();
        System.out.println(watch.elapsed(TimeUnit.MILLISECONDS));

        for (int i = 0; i < 5; i++) {
            sleep(500);
            System.out.println(watch.elapsed(TimeUnit.MILLISECONDS));
        }
    }

    @Test
    public void testStopwatchAndStop() {
        Stopwatch watch = Stopwatch.createStarted();
        System.out.println("开始时间:" + watch.elapsed(TimeUnit.MILLISECONDS));

        for (int i = 0; i < 2; i++) {
            sleep(100);
            System.out.println("100ms后,实时计算elapsedNanos:" + watch.elapsed(TimeUnit.MILLISECONDS)); // 实时计算elapsedNanos

            sleep(400);
            watch.stop(); // 更新elapsedNanos. 将本次"start() 到 stop()"之间的耗时累加到elapsedNanos.
            System.out.println("400ms后,直接返回elapsedNanos:" + watch.elapsed(TimeUnit.MILLISECONDS)); // 调用stop()已经计算好了elapsedNanos

            // doSometing(). stop()与start()之间的时间不会计算在内,忽略
            sleep(500);
            System.out.println("stop()之后等待500ms,elapsedNanos与上次相同:" + watch.elapsed(TimeUnit.MILLISECONDS)); // 调用stop()已经计算好了elapsedNanos

            watch.start(); // 更新startTick为当前时间
        }
    }

    private void sleep(long millis) {
        try {
            TimeUnit.MILLISECONDS.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

testStopwatchAndStop()运行结果:

开始时间:0
100ms后,实时计算elapsedNanos:99
400ms后,直接返回elapsedNanos:499
stop()之后等待500ms,elapsedNanos与上次相同:499
100ms后,实时计算elapsedNanos:599
400ms后,直接返回elapsedNanos:999
stop()之后等待500ms,elapsedNanos与上次相同:999

---转载本站文章请注明作者和出处 二进制之路(binarylife.icu),请勿用于任何商业用途---

留下评论