文章目录
- 一、google-guava工具包简介
- 1、概述
- 2、引包
- 二、常用工具类
- 1、LoadingCache 缓存
- 2、RateLimiter限流器
- 3、EventBus事件总线
- (1)基本使用
- (2)多消费者
- (3)DeadEvent
- (4)AsyncEventBus异步消费
- 参考资料
一、google-guava工具包简介
1、概述
Guava项目包含我们在基于Java的项目中所依赖的几个Google核心库:集合、缓存、原语支持、并发库、公共注释、字符串处理、I/O等等。这些工具中的每一个都被谷歌员工在生产服务中每天使用,也被许多其他公司广泛使用。
2、引包
想要使用guava很简单,只需要引入依赖包就可以使用了:
<!-- maven -->
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
// gradle
// https://mvnrepository.com/artifact/com.google.guava/guava
implementation group: 'com.google.guava', name: 'guava', version: '32.1.3-jre'
二、常用工具类
1、LoadingCache 缓存
LoadingCache 在实际场景中有着非常广泛的使用,通常情况下如果遇到需要大量时间计算或者缓存值的场景,就应当将值保存到缓存中。LoadingCache 和 ConcurrentMap 类似,但又不尽相同。
最大的不同是 ConcurrentMap 会永久的存储所有的元素值直到他们被显示的移除,但是 LoadingCache 会为了保持内存使用合理会根据配置自动将过期值移除。
import com.google.common.cache.*;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class CacheMain {
public static void main(String[] args) {
RemovalListener<String, String> removalListener = new RemovalListener<String, String>() {
public void onRemoval(RemovalNotification<String, String> removal) {
System.out.println("移出了key:" + removal.getKey());
System.out.println("移出了key:" + removal.getCause());
System.out.println("移出了key:" + removal.getValue());
}
};
// 定义缓存,key-value的形式
LoadingCache<String, String> caches = CacheBuilder
.newBuilder()
// 缓存最大值,超过最大值会移出
.maximumSize(10)
// 自上次读取或写入时间开始,10分钟过期
.expireAfterAccess(10, TimeUnit.MINUTES)
// 自上次写入时间开始,10分钟过期
.expireAfterWrite(10, TimeUnit.MINUTES)
// 基于权重驱逐key
.maximumWeight(100000)
.weigher(new Weigher<String, String>() {
public int weigh(String k, String v) {
// 获取权重
return v.getBytes().length;
}
})
// 设置移出监听器(同步的,高并发可能会阻塞)
.removalListener(removalListener)
// 构造获取缓存数据的方法
.build(
new CacheLoader<String, String>() {
public String load(String key) {
return createValue(key);
}
});
// 需要检查异常
try {
System.out.println(caches.get("key"));
} catch (ExecutionException e) {
e.printStackTrace();
}
// 不会检查异常
caches.getUnchecked("key");
caches.getIfPresent("key");
// 使用Callable,将call()方法的返回值作为缓存
try {
System.out.println(caches.get("ckey", new Callable<String>() {
@Override
public String call() throws Exception {
return "Callable";
}
})); // Callable
System.out.println(caches.get("ckey")); // Callable
} catch (ExecutionException e) {
e.printStackTrace();
}
// 直接放缓存
caches.put("key2", "v2");
// 将缓存作为ConcurrentMap输出
System.out.println(caches.asMap());
// 删除key
caches.invalidate("key");
caches.invalidateAll(Arrays.asList("key", "key1"));
caches.invalidateAll();
// 清除过期缓存,一般在写入的时候才会清理部分缓存,如果需要,可以手动清除一下
caches.cleanUp();
}
private static String createValue(String key) {
System.out.println("获取value");
return "value";
}
}
2、RateLimiter限流器
常用方法:
/**
* 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求
* 当请求到来的速度超过了permitsPerSecond,保证每秒只处理permitsPerSecond个请求
* 当这个RateLimiter使用不足(即请求到来速度小于permitsPerSecond),会囤积最多permitsPerSecond个请求
*/
public static RateLimiter create(double permitsPerSecond)
/**
* 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求
* 还包含一个热身期(warmup period),热身期内,RateLimiter会平滑的将其释放令牌的速率加大,直到起达到最大速率
* 同样,如果RateLimiter在热身期没有足够的请求(unused),则起速率会逐渐降低到冷却状态
*
* 设计这个的意图是为了满足那种资源提供方需要热身时间,而不是每次访问都能提供稳定速率的服务的情况(比如带缓存服务,需要定期刷新缓存的)
* 参数warmupPeriod和unit决定了其从冷却状态到达最大速率的时间
*/
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit);
//每秒限流 permitsPerSecond,warmupPeriod 则是数据初始预热时间,从第一次acquire 或 tryAcquire 执行开时计算
public static RateLimiter create(double permitsPerSecond, Duration warmupPeriod)
//获取一个令牌,阻塞,返回阻塞时间
public double acquire()
//获取 permits 个令牌,阻塞,返回阻塞时间
public double acquire(int permits)
// 获取一个令牌,如果获取不到立马返回false
public boolean tryAcquire()
//获取一个令牌,超时返回
public boolean tryAcquire(Duration timeout)
获取 permits 个令牌,超时返回
public boolean tryAcquire(int permits, Duration timeout)
RateLimiter limiter = RateLimiter.create(2, 3, TimeUnit.SECONDS);
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
System.out.println("get one permit cost time: " + limiter.acquire(1) + "s");
--------------- 结果 -------------------------
get one permit cost time: 0.0s
get one permit cost time: 1.331672s
get one permit cost time: 0.998392s
get one permit cost time: 0.666014s
get one permit cost time: 0.498514s
get one permit cost time: 0.498918s
get one permit cost time: 0.499151s
get one permit cost time: 0.488548s
因为RateLimiter滞后处理的,所以第一次无论取多少都是零秒
可以看到前四次的acquire,花了三秒时间去预热数据,在第五次到第八次的acquire耗时趋于平滑
3、EventBus事件总线
EventBus是Guava的事件处理机制,是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现。对于事件监听和发布订阅模式,EventBus是一个非常优雅和简单解决方案,我们不用创建复杂的类和接口层次结构。
(1)基本使用
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
public class EventBusMain {
/**
* 定义消息实体,EventBus只支持一个消息参数,多个参数需要自己封装为对象
*/
public static class MyEvent {
private String message;
public MyEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
/**
* 定义消费者
*/
public static class EventListener {
// 使用@Subscribe 可以监听消息
@Subscribe
public void handler(MyEvent event) {
System.out.println("消费者接收到消息:" + event.getMessage());
}
}
public static void main(String[] args) {
// 定义EventBus
EventBus eventBus = new EventBus("test");
// 注册消费者
EventListener listener = new EventListener();
eventBus.register(listener);
// 发送消息
eventBus.post(new MyEvent("消息1"));
eventBus.post(new MyEvent("消息2"));
eventBus.post(new MyEvent("消息3"));
}
}
结果:
消费者接收到消息:消息1
消费者接收到消息:消息2
消费者接收到消息:消息3
(2)多消费者
在消费者的方法中,会自动根据参数的类型,进行消息的处理。
/**
* 定义消费者
*/
public static class EventListener {
// 使用@Subscribe 可以监听消息
@Subscribe
public void handler1(MyEvent event) {
System.out.println("消费者接收到MyEvent1消息:" + event.getMessage());
}
@Subscribe
public void handler2(MyEvent event) {
System.out.println("消费者接收到MyEvent2消息:" + event.getMessage());
}
@Subscribe
public void handler3(String event) {
System.out.println("消费者接收到String消息:" + event);
}
@Subscribe
public void handler4(Integer event) {
System.out.println("消费者接收到Integer消息:" + event);
}
}
public static void main(String[] args) {
// 定义EventBus
EventBus eventBus = new EventBus("test");
// 注册消费者
EventListener listener = new EventListener();
eventBus.register(listener);
// 发送消息
eventBus.post(new MyEvent("消息1"));
eventBus.post("消息2");
eventBus.post(99988);
}
结果:
消费者接收到MyEvent2消息:消息1
消费者接收到MyEvent1消息:消息1
消费者接收到String消息:消息2
消费者接收到Integer消息:99988
(3)DeadEvent
/**
* 如果没有消息订阅者监听消息, EventBus将发送DeadEvent消息
*/
public static class DeadEventListener {
// 消息类型必须是DeadEvent
@Subscribe
public void listen(DeadEvent event) {
System.out.println(event);
}
}
public static void main(String[] args) {
// 定义EventBus
EventBus eventBus = new EventBus("test");
// 注册消费者
EventListener listener = new EventListener();
eventBus.register(listener);
eventBus.register(new DeadEventListener()); // 注册DeadEventListener
// 发送消息
eventBus.post(new MyEvent("消息1"));
eventBus.post("消息2");
eventBus.post(99988);
eventBus.post(123.12D); // 如果发送的消息,没有消费者,就会到DeadEvent
}
执行结果
消费者接收到MyEvent2消息:消息1
消费者接收到MyEvent1消息:消息1
消费者接收到String消息:消息2
消费者接收到Integer消息:99988
DeadEvent{source=EventBus{test}, event=123.12}
(4)AsyncEventBus异步消费
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.Subscribe;
import java.util.concurrent.Executors;
public class EventBusMain2 {
/**
* 定义消费者
*/
public static class EventListener {
// 使用@Subscribe 可以监听消息
@Subscribe
public void handler1(String event) {
System.out.println("消费者1接收到String消息:" + event + ",线程号:" + Thread.currentThread().getId());
}
@Subscribe
public void handler2(String event) {
System.out.println("消费者2接收到String消息:" + event + ",线程号:" + Thread.currentThread().getId());
}
}
public static void main(String[] args) {
// 定义EventBus,消费者消费是异步消费
AsyncEventBus eventBus = new AsyncEventBus("test", Executors.newFixedThreadPool(10));
// 注册消费者
EventListener listener = new EventListener();
eventBus.register(listener);
// 发送消息
System.out.println("发送消息,线程号:" + Thread.currentThread().getId());
eventBus.post("消息2");
}
}
执行结果:
发送消息,线程号:1
消费者2接收到String消息:消息2,线程号:21
消费者1接收到String消息:消息2,线程号:22
参考资料
官网:https://github.com/google/guava
官方文档:https://github.com/google/guava/wiki