guava_缓存

本文详细介绍Guava提供的缓存机制,包括MapMaker创建ConcurrentMap、Cache与LoadingCache的基本用法、CacheBuilder配置项、CacheLoader加载策略、CacheStats性能统计及RemovalListener事件监听等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Guava Cache

在软件开发的过程,缓存是一个非常重要的话题。 在稍微复杂的开发过程中,我们基本上是不可能不使用到缓存的。 至少我们会使用Map去存储一些东西。 这其实就是一个最简单的缓存。 Guava给我们提供了比简单的使用HashMap更强大更灵活的功能,但是和专业的缓存工具相比,(EHCache,Memcached)功能还有些不足, 那么这一章,我们将覆盖Guava cache的下面几个方面:

– 使用MapMaker类创建ConcurrentMap实例
– 使用CacheBuilder的链式编程的方式创建LoadingCache和Cache实例
– 使用CacheBuilderSpec类通过格式化的字符串创建CacheBuilder实例
– 使用LoadingCache的实例CacheLoader通过指定的key获取对应的值
– 使用CacheStats类查看Cache的使用状态
– 使用RemovalListener类接受一个entry从map中删除的事件

下面让我们逐一介绍一下:

MapMaker

MapMaker类在com.google.common.collect包中,我们来看一下怎样使用MapMaker的链式编程快速创建一个ConcurrentHashMap。

ConcurrentMap<String,Book> books = new
MapMaker().concurrencyLevel(2)
.softValues()
.makeMap();

上面的例子中,我们创建了key为String类型,值为Book类型的ConcurrentHashMap. 其中concurrencyLevel()方法是为了指定同时可以用几个线程修改map的值。 softValues() 会将value包装成SoftReference对象,这样发生内存不够使用时,就会被垃圾回收自动回收。 还有其他的一些方法,比如weakKeys() weakValues(), 但是没有softKeys()这是为什么? 还要去看一下java的引用。 当我们使用weakReference或则SoftReference时,无论是key还是value被回收了,map中都会将整个entry移出,这样就不是出现key在value不在,或则value在key不在的情况。

Guava Caches

在我们使用和了解CacheBuilders之前,我们先了解一些关于Cache的背景知识, 在Guava中,我们有两个基本的接口 Cache 和LoadingCache,其中LoadingCache接口继承了Cache接口

Cache

Cache接口提供了基本的key到value的映射。另外也提供了一些比HashMap更好用的接口。 谈到Cache,我们一般的做法是给定一个key,cache会返回这个key对应的value,如果没有找到对应的value,那么将返回NULL. 如果我们想替换一个key/value值,我们可以调用如下方法:

put(key,value);

这里我们将key和value关联到cache或map中。 Cache接口中有传统的put方法,但是也有自己的特有的方法:

V value = cache.get(key, Callable<? Extends V> value);

上面的方法中我们将根据key获取值,如果值在缓存中,就直接返回,如果不在那么就调用Callable获取值,并将获取到的值和对应的key关联,并返回得到的值。 这个方法就完成下面这段代码的功能:

value = cache.get(key);
if(value == null){
value = someService.retrieveValue();
cache.put(key,value);
}

上面的那个方法的使用中,我们需要传入一个Callable对象,但是一般情况下,我们会使用一个匿名的内部类, 这里有个小技巧,如果我们不使用内部类,我们有其他方法吗? 我们可以使用Callables类, 在com.googele.common.util.concurrent包中, Callables中有一个方法可以方便的返回一个值:

Callable<String> value = Callables.returning("Foo");

上面的这条语句中,returning方法会创建返回一个Callable实例,并且当get方法调用时,会返回给定的’Foo’值。 这样的话,我们就可以替换上面例子的实现:

cache.get(key,Callables.returning(someService.retrieveValue());

这里我们要记住,如果值在缓存中,就直接返回, 如果我们期望 存在就返回,不存在就返回null. 那么可以使用getIfPresent(key) 方法。 Guava Cache中也有方法将值失效。
– invalidate(key):调用这个方法可以将指定的key的值丢弃
– invalidateAll(): 这个方法丢弃缓存中的所有值
– invalidateAll(Iterable

Loading Cache

LoadingCache继承了Cache实例,扩展了自己的方法,看一下下面的例子:

Book book = loadingCache.get(id);

在上面的例子中,如果book对象目前还不在缓存中,LoadingCache可以知道怎样去获取对象,存储对象并返回.

Loading values

LoadingCache的设计是线程安全的,针对同一个key的并发调用是会被block的,一旦value获取到了,这个value值就会返回给调用的get方法,但是如果调用get是多个不同的key那么我们就可以进行并发调用:

ImmutableMap<key,value> map = cache.getAll(Iterable<? Extends
key>);

getAll 返回了一个ImmutableMap 包含了指定的keys 和对应的values。返回的对象,有可能是直接从缓存中取得,有可能是新获取到的,有可能一部分是新获取的一部分是从缓存中取得。

Refreshing values in the cache

LoadingCache 提供了刷新缓存的方法:

refresh(key);

调用loadingcache的refresh方法,loadingcache会重新根据key获取值。 值得注意的地方是: 原来的值会一直等到新值回来才会被替换,在获取新值的过程如果出现异常,原来的值不会被丢弃。

CacheBuilder

CacheBuilder 提供通过建造者方式创建Cache和LoadingCache实例。 Cache实例提供了很多方法。 下面通过例子来感觉一下怎样使用Guava Cache。 第一个例子中我们展示了怎样从Cache中是一个entry失效。

LoadingCache<String,TradeAccount> tradeAccountCache =
CacheBuilder.newBuilder()
.expireAfterWrite(5L, TimeUnit.Minutes)
.maximumSize(5000L)
.removalListener(new
TradeAccountRemovalListener())
.ticker(Ticker.systemTicker())
.build(new CacheLoader<String, TradeAccount>() {
@Override
public TradeAccount load(String key) throws
Exception {
return
tradeAccountService.getTradeAccountById(key);
}
});

其中 TradeAccount的定义如下:

public class TradeAccount {
private String id;
private String owner;
private double balance;
}

下面我们分析一下上面的这个例子:
1. 首先是expireAfterWrite方法,这个方法会自动在指定时间之后移出对应的entry。 在这个例子中是5分钟。
2. maximumSize() 指定了缓存的最大的数量。
3. 我们调用了RemovalListener增加了一个removalListener实例,当一个entry移出后,对调用removalListener方法。
4. 通过ticker方法增加了一个Ticker实例,提供了纳秒级的服务
5. 最后调用了build 方法,传入了一个CacheLoader实例,这样当一个key对应的value不在缓存中时,通过这个方法可以获取到对应的value。

下面的例子中,我们将了解基于最后访问时间使得entry失效:

LoadingCache<String,Book> bookCache = CacheBuilder.newBuilder()
.expireAfterAccess(20L,TimeUnit.MINUTES)
.softValues()
.removalListener(new BookRemovalListener())
.build(new CacheLoader<String, Book>() {
@Override
public Book load(String key) throws Exception
{
return bookService.getBookByIsbn(key);
}
});

在这个例子中,我们的做法有点不一样,下面我们来看一下这个例子。

  1. 首先我们调用expireAfterSeconds方法,当一个entry在20分钟没有被访问,就会自动从cache中移出。
  2. 使用jvm的softReference 软引用来代替我们限制cache的大小。 这样当memory不够用时,jvm会自动将entry从cache中移出。 jvm的移出规则是LRU (least-recently-used)算法。
  3. 最后我们加上了RemovalListener 来监听被移出的entry。

下面来看最后一个例子,看样子在guava中怎样自动的刷新缓存。

LoadingCache<String,TradeAccount> tradeAccountCache =
CacheBuilder.newBuilder()
.concurrencyLevel(10)
.refreshAfterWrite(5L,TimeUnit.SECONDS)
.ticker(Ticker.systemTicker())
.build(new CacheLoader<String,
TradeAccount>() {
@Override
public TradeAccount load(String key)
throws Exception {
return
tradeAccountService.getTradeAccountById(key);
}
});

最后一个例子中,我们同样做了一些改变,下面我们解释一下具体的改变:

  1. 首先指定了最多可以并发修改的线程数,我们这里指定了10,如果没有指定默认值是4.
  2. 我们使用在指定时间后自动刷新value的方法替代显示的调用刷新,触发自动更新的条件是这个value被访问并且已经超过指定的时间。
  3. tricker 了纳秒级的触发器
  4. 最后我们传递了一个CacheLoad实例用于获取key对应的value。

CacheBuilderSpec

CacheBuilderSpec 类可以通过指定一个配置的字符串创建一个CacheBuilder实例。 但是这样有个不好的地方,就是我们不能在编译期发现错误,只能在运行期发现错误。 下面是一个用于创建CacheBuilderSpec的字符串:

String configString = "concurrencyLevel=10,refreshAfterWrite=5s"

这个字符串指定了并发更新线程数是10,访问5s后自动刷新,指定时间的方式可以通过(s,m,h,d)分别代表秒,分,时,天。 暂时还没有办法设置毫秒及纳秒级别。 指定了配置的字符串后,我们就可以创建CacheBuilderSpec。 创建方式如下:

CacheBuilderSpec spec = CacheBuilderSpec.parse(configString);

接着我们可以使用CacheBuilder的form方法获取CacheBuilder实例。 完整的例子如下:

String spec =
"concurrencyLevel=10,expireAfterAccess=5m,softValues";
CacheBuilderSpec cacheBuilderSpec =
CacheBuilderSpec.parse(spec);
CacheBuilder cacheBuilder =
CacheBuilder.from(cacheBuilderSpec);
cacheBuilder.ticker(Ticker.systemTicker())
.removalListener(new TradeAccountRemovalListener())
.build(new CacheLoader<String, TradeAccount>() {
@Override
public TradeAccount load(String key) throws
Exception {
return
tradeAccountService.getTradeAccountById(key);
}
});

一般这种指定字符串的方式用在命令行或则配置文件中。

CacheLoader

我们在之前的例子中,已经看到了CacheLoader的使用方法,但是还有一些细节还没覆盖到。 CacheLoader 是一个抽象类,因为里面的load方法是一个抽象方法, 在CacheLoader中还有一个loadAll方法. loadAll方法接受一个Iterable参数,其实内部也是循环调用load方法。 CacheLoader中还有两个比较熟悉的静态方法.
1. CacheLoader.from(Function

CacheStats

到目前为止,我们已经知道了很多cache的算法,但是我们现在想知道这些cache算法的工作情况. 但是我们要知道收集cache情况会或多或少降低cache的性能。 要监控cache的性能信息,我们只需要在创建cache实例的时候加上recordStats()方法:

LoadingCache<String,TradeAccount> tradeAccountCache =
CacheBuilder.newBuilder()
.recordStats()

我们采用我们比较熟悉的链式编程的方式加上了对cache的监控。 获取监控数据我们只需要要调用stats()方法得到一个CacheStatus实例。 代码示例:

CacheStats cacheStats = cache.stats();

下面是可以从CacheStats中获取到的监控信息:
– 每次获取新的值的时间
– 缓存的命中率
– 缓存失败率
– 值被剔除的次数

还有很多其他的信息,大家可以看cacheStats的接口文档。

RemovalListener

在CacheBuilder的例子中我们已经看到怎样使用RemovalListener。 就想名字所描述的一样,通知发生在当一个entry没移出时。 和java里面的其他listener接口一样,RemovalListener也有一个onRemoval方法。这个方法接受一个RemovalNotification对象,RemovalListener接受参数化的配置 RemovalListener

RemovalNotification

RemovalNotification对象是RemovalListener接受的参数,RemovalNotification对象实现了Map.Entry接口,这样我们就可以获取到被删除的key和value的值,但是值得注意的是我们有可能获取的key或者value的值为空。 因为有可能这些值被jvm回收了。 为了获取被移出的原因我们可以调用getCause()方法。 下面是可能被回收的原因的枚举:

– COLLECTED: 这个是key或者value被垃圾回收了
– ECPRED: 这个是这个entry最后一次访问时间或则最后一次写入时间超时了
– EXPLICIT: 这个表示是用户手动移出
– REPLACED: 这个表明 这个entry没有被移出,但是 value被重新写过
– SIZE: 这个表明因为cache的空间不足原因被移出

一般的,如果我们想在enrty被移出的时候 做一写操作,我们最好采取异步的方式、

RemovalListeners

RemovalListeners类是帮助我们异步处理移出后的动作。 使用方式如下:

RemovalListener<String,TradeAccount> myRemovalListener = new
RemovalListener<String, TradeAccount>() {
@Override
public void onRemoval(RemovalNotification<String,
TradeAccount> notification) {
//Do something here
}
};
RemovalListener<String,TradeAccount> removalListener =
RemovalListeners.asynchronous(myRemovalListener,executorService);

这里我们在asynchronous方法中传入了 RemovalListener 和 ExecutorService 参数。 这样就会调用executorService 帮助我们异步的处理 移出事件。 但是要注意的是,这个方法的要在我们调用 CacheBuilder.addRemovalListener之前。

总结

这一章中我们学习了GUAVA的使用方法。 我们学习
1. MapMaker创建一个ConcurrentMap
2. 我们学习的Cache和LoadingCache的一些常用方法
3. 学习了CacheBuilder的一些常用配置
4. CacheLoader 是LoadingCache的核心组件
5. CacheStats 统计Cache的性能信息
6. RemovalListener类 用来接受entry被移出事件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值