欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-03-10 16:03:32
RateLimiter是Google Guava框架的一个限速器,通常用于控制对某个资源的访问速率。限速常见的有两种实现方式,一种是令牌桶,另一种是漏桶。RateLimiter选择了令牌桶作为其底层实现,按照固定速率投放令牌,同时支持突发流量。本篇先来解读一下Ticker和Stopwatch,它们是RateLimiter底层时间计算的基础。代码基于Guava 23.0版本。TickerTicker的意思钟表,里面只有一个read()方法,用来获取当前时间。核心代码解读:public abst...

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),请勿用于任何商业用途—

个人博客:https://binarylife.icu/

公众号:二进制之路

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

本文地址:https://blog.csdn.net/zhanjia/article/details/108806656

相关标签: Java google