本地缓存LoadingCache使用【详解】
一、背景
最近来到了新的团队,发现了一个好用的东西-Guava的LoadingCache本地缓存,我们都知道Guava是一个非常好用的工具集合,这次认识到了一个非常好用的本地缓存-LoadingCache。
我们知道缓存有多种类型,比如常见的分布式缓存、Redis缓存等,然而像loadingCache本地缓存是比较轻的,我们都知道内存不贵,在高性能、并发的面前就特别占优势。
二、初识LoadingCache
2.1 LoadingCache简介
我们都知道Guava是一个编程工具类库,其中包含了很多高质量高性能的工具类和方法。其中,LoadingCache是一个带有自动加载功能的缓存,它可以加载缓存中不存在的数据,本质其实是一个键值对(key-value)的缓存,可以通过key获取到对应的缓存值value。
2.2 LoadingCache的核心接口
在Guava中使用LoadingCache时,主要要关注两个接口:CacheLoader和LoadingCache。其中,CacheLoader承担的是将键映射到值的查询逻辑工作,而LoadingCache是CacheLoader的衍生接口。它增加了一些额外的操作,如自动加载、异步加载、缓存刷新等。
首先,定义一下CacheLoader接口及其方法:
public interface CacheLoader {
V load(K key) throws Exception;
}
其中K代表键的类型,V代表值的类型。load()方法即为查询逻辑方法,接收一个Key作为参数,返回与该Key相关的Value。如果CacheLoader逻辑中没有命中该Key对应的Value,则将返回null。
接下来是LoadingCache接口及其方法:
public interface LoadingCache extends Cache {
V get(K key) throws ExecutionException;
ImmutableMap getAll(Iterable keys) throws ExecutionException;
void refresh(K key);
ConcurrentMap asMap();
}
其中Cache接口是LoadingCache的父接口,它定义了一些基本的缓存操作方法。而LoadingCache则增加了一些自动加载、缓存刷新等操作。
-
get(K key)方法定义了默认的基本缓存操作,如果Cache中不包含该Key对应的Value,将自动调用CacheLoader.load(K key)方法进行自动加载。如果没有定义CacheLoader,将抛出一个UncheckedExecutionException异常。
-
getAll(Iterable<? extends K> keys)则扩展了get(K key),可以同时获取多组Key-Value对的结果。
-
refresh(K key)方法可以对特定Key的Value进行刷新,即调用对应Key的CacheLoader.load(K key)重新加载并替换原值。
-
asMap()方法则返回对应的ConcurrentMap对象,可以使用常规的Map方法来对缓存中的数据进行操作。
三、LoadingCache的缓存策略
LoadingCache提供了基本缓存策略、基于引用缓存策略、基于提醒缓存策略
3.1 LoadingCache的基本缓存策略
Guava提供了两种基本缓存策略,分别为缓存数量大小和基于缓存时间。
3.1.1 基于缓存数量大小
基于大小的缓存是指,当缓存中的Key-Value对数量超出一定的限制时,LoadingCache会自动剔除旧的Key-Value对,以保持缓存大小不超过限制。可以使用maximumSize(long)方法来指定缓存的最大大小,如:
LoadingCache cache =
CacheBuilder.newBuilder().maximumSize(100).build(new CacheLoader() {
public String load(String key) throws Exception {
return key + "->value";
}
});
3.1.2 基于缓存时间
基于时间的缓存是指,当缓存中的Key-Value对超过限制时间后,LoadingCache会自动剔除该Key-Value对。可以使用expireAfterWrite(long, TimeUnit)方法来指定缓存过期时间。如:
LoadingCache cache =
CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(new CacheLoader() {
public String load(String key) throws Exception {
return key + "->value";
}
});
3.2 基于引用的缓存策略
基于引用的缓存策略是指,缓存中的Key-Value对的引用关系从而被决定是否从缓存中清除。Guava提供了两种基于引用的缓存策略,分别是弱引用和软引用。
弱引用是指,当Java虚拟机的垃圾回收器扫描到该Key-Value对的Key只被弱引用引用时,将自动从缓存中清除该Key-Value对。可以使用weakKeys()方法启用弱引用:
LoadingCache cache =
CacheBuilder.newBuilder().weakKeys().build(new CacheLoader() {
public String load(String key) throws Exception {
return key + "->value";
}
});
软引用是指,当Java虚拟机的垃圾回收器扫描到该Key-Value对的Key被软引用引用并且内存不足时,将自动从缓存中清除该Key-Value对。可以使用softValues()方法启用软引用:
LoadingCache cache =
CacheBuilder.newBuilder().softValues().build(new CacheLoader() {
public String load(String key) throws Exception {
return key + "->value";
}
});
3.3 基于提醒的缓存策略
基于提醒的缓存策略是指,缓存中的Key-Value对在特定条件下会被自动剔除。Guava提供了两种基于提醒的缓存策略,分别是基于写入提醒和基于访问提醒。其中,写入提醒是指,在写入某个Key-Value对时,通知外部任务,以便执行对该Key-Value对的一些处理。访问提醒是指,在特定PerformFunction上访问一个Key-Value对时,通知外部任务,以便记录下该Key-Value对的访问情况。可以使用CacheBuilder提供的removalListener(RemovalListener)方法来启用缓存的提醒策略。
下面是写入提醒的例子:
Cache cache =
CacheBuilder.newBuilder()
.maximumSize(1000).recordStats()
.removalListener(new RemovalListener() {
public void onRemoval(RemovalNotification removalNotification) {
System.out.println(removalNotification.getKey() + " was removed, cause is " + removalNotification.getCause());
}
}).build();
四、线程安全
一个缓存框架的线程安全是非常重要的。Guava的LoadingCache在多线程操作时,是线程安全的。在它的内部实现中使用了ConcurrentHashMap作为容器,对缓存的更新和读取都使用了并发锁机制。
以下是一个LoadingCache的多线程并发示例:
public class LoadingCacheTest {
private static final LoadingCache CACHE = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader() {
public String load(String key) throws Exception {
return key + "->loaded";
}
});
public static void main(String[] args) throws InterruptedException, ExecutionException {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
try {
System.out.println(Thread.currentThread().getName() + ": " + CACHE.get("key"));
TimeUnit.MILLISECONDS.sleep(50L);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "Thread-1").start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 50; i++) {
try {
System.out.println(Thread.currentThread().getName() + ": " + CACHE.get("key2"));
TimeUnit.MILLISECONDS.sleep(100L);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "Thread-2").start();
}
}
五、总结
本文详细介绍了Google Guava中的LoadingCache的实现原理。LoadingCache是一种带有自动加载功能的缓存,可以实现高效的缓存访问和更新。在使用LoadingCache时,要熟悉其核心接口和各种缓存策略,以及线程安全的问题。同时,在实际使用LoadingCache时,需要根据具体的业务场景和数据特点制定相应的缓存策略和CacheLoader处理逻辑。