面试题答案
一键面试Guava Cache的LoadingCache接口在缓存数据加载方面的优势
- 自动加载:LoadingCache接口实现了CacheLoader,当调用
get(key)
方法且缓存中不存在对应key时,会自动调用CacheLoader
的load
方法加载数据并放入缓存,极大简化了缓存加载逻辑。 - 批量加载:
CacheLoader
还提供了loadAll
方法,当需要加载多个键值对时,可以一次性加载,提升效率并减少资源开销。 - 可配置性:可以通过
CacheBuilder
对缓存进行各种配置,如设置过期时间、最大缓存容量等,灵活满足不同应用场景需求。
在高并发场景下利用LoadingCache避免缓存击穿问题
缓存击穿指的是大量并发请求同时访问一个过期的缓存键,导致所有请求都直接访问后端数据库等数据源,对后端造成巨大压力。LoadingCache在高并发场景下避免缓存击穿的原理是,当一个key过期或不存在时,只有一个线程会去真正加载数据,其他线程会等待这个线程加载完成并将数据放入缓存后,直接从缓存获取数据。
以下是代码示例:
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class LoadingCacheExample {
// 模拟数据库查询
private static int getDataFromDB(String key) {
System.out.println("从数据库获取数据: " + key);
return Integer.parseInt(key);
}
public static void main(String[] args) {
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.build(
new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) throws Exception {
return getDataFromDB(key);
}
}
);
// 模拟高并发请求
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
final String key = String.valueOf(i);
threads[i] = new Thread(() -> {
try {
System.out.println("线程 " + Thread.currentThread().getName() + " 获取到数据: " + cache.get(key));
} catch (ExecutionException e) {
e.printStackTrace();
}
});
threads[i].start();
}
// 等待所有线程执行完毕
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在上述代码中,LoadingCache
在获取缓存值时,如果缓存中不存在该值,会调用CacheLoader
的load
方法从数据库(这里用getDataFromDB
模拟)加载数据。在高并发场景下,只有一个线程会执行load
方法,其他线程会等待这个线程加载完成并将数据放入缓存,从而避免了缓存击穿问题。同时,通过CacheBuilder
设置了缓存的过期时间为10秒,在过期后同样能保证只有一个线程去重新加载数据。