
本文旨在解决caffeine缓存中值存储后无法正确获取(返回null)的常见问题。通过深入分析`weakkeys()`、`weakvalues()`以及缓存实例的作用域,文章揭示了导致值失效的核心原因,并提供了将缓存声明为`static final`并移除弱引用配置的解决方案。教程将详细阐述其原理,并给出示例代码,帮助开发者构建稳定可靠的caffeine缓存。
在使用Caffeine构建本地缓存时,开发者可能会遇到一个令人困惑的问题:即使通过put()方法存储了值,随后尝试通过getIfPresent()获取时却返回null。这通常发生在以下场景中:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class MyCacheService {
// 假设这是一个普通的实例字段
private Cache<Long, SmsData> codeCache = Caffeine.newBuilder()
.expireAfterWrite(24, TimeUnit.HOURS)
.weakKeys() // 弱引用键
.weakValues() // 弱引用值
.build();
public void storeSmsData(Long id, int currentSendCount) {
SmsData data = new SmsData();
data.setSendCount(++currentSendCount);
data.setCheckCount(0);
codeCache.put(id, data);
System.out.println("Stored data for id: " + id + ", data: " + data);
}
public SmsData retrieveSmsData(Long id) {
SmsData data = codeCache.getIfPresent(id);
System.out.println("Retrieved data for id: " + id + ", data: " + data);
return data;
}
// 模拟数据类
static class SmsData {
int sendCount;
int checkCount;
public int getSendCount() { return sendCount; }
public void setSendCount(int sendCount) { this.sendCount = sendCount; }
public int getCheckCount() { return checkCount; }
public void setCheckCount(int checkCount) { this.checkCount = checkCount; }
@Override
public String toString() {
return "SmsData{sendCount=" + sendCount + ", checkCount=" + checkCount + '}';
}
}
public static void main(String[] args) throws InterruptedException {
MyCacheService service = new MyCacheService();
Long testId = 123L;
service.storeSmsData(testId, 1);
// 短暂等待,模拟GC或线程切换
// Thread.sleep(100);
SmsData retrievedData = service.retrieveSmsData(testId);
if (retrievedData == null) {
System.out.println("Error: Data for id " + testId + " was null!");
}
}
}在上述代码中,尽管我们调用了put()方法,但getIfPresent()很可能返回null。这通常是由两个主要因素导致的:弱引用配置和缓存实例的生命周期。
Caffeine提供了weakKeys()和weakValues()方法,允许缓存使用弱引用来持有键和值。在Java中,弱引用是一种特殊的引用类型,它不会阻止垃圾收集器回收其引用的对象。这意味着,如果一个对象只被弱引用所引用,并且没有其他强引用指向它,那么垃圾收集器在下一次运行时就会回收这个对象。
对于大多数缓存场景,我们期望缓存能够“强”持有其存储的键和值,直到它们因过期策略(如expireAfterWrite)或容量限制而被主动驱逐。使用弱引用通常是为了实现内存敏感的缓存,例如,当缓存的目的是作为其他地方已经强引用的对象的“影子”副本,或者你希望当内存紧张时,缓存能够自动释放那些不再被应用程序其他部分使用的对象。然而,如果不理解其含义,这会导致缓存行为与预期不符。
如果Cache实例本身是一个普通的对象字段(如上述示例中的private Cache<Long, SmsData> codeCache),那么它会随着其所在对象的生命周期而存在。如果包含Cache的MyCacheService对象在应用程序中被频繁创建和销毁,或者该对象本身被垃圾回收,那么其内部的Cache实例也会随之消失,导致所有存储的数据丢失。
对于一个应用程序级别的缓存,我们通常希望它在应用程序的整个生命周期内都保持活跃,并且其内部数据不会因为缓存实例本身被回收而丢失。
解决上述问题的方法相对直接:确保缓存实例的生命周期与应用程序保持一致,并移除不必要的弱引用配置。
将Cache实例声明为static final具有以下优点:
通过这种方式,codeCache实例将伴随应用程序的整个生命周期,直到应用程序终止,从而避免了缓存实例本身被垃圾回收的问题。
除非有明确的、经过深思熟虑的理由需要弱引用行为,否则应移除weakKeys()和weakValues()配置。默认情况下,Caffeine会使用强引用来持有键和值,这正是大多数缓存场景所期望的行为。这样,只要缓存本身存在,并且键值对没有因过期或容量限制而被驱逐,它们就会被强引用持有,不会被垃圾回收。
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class MyCacheService {
// 修正:声明为 static final,并移除 weakKeys() 和 weakValues()
private static final Cache<Long, SmsData> codeCache = Caffeine.newBuilder()
.expireAfterWrite(24, TimeUnit.HOURS) // 保持过期策略
// .weakKeys() // 移除此行
// .weakValues() // 移除此行
.build();
public void storeSmsData(Long id, int currentSendCount) {
SmsData data = new SmsData();
data.setSendCount(++currentSendCount);
data.setCheckCount(0);
codeCache.put(id, data);
System.out.println("Stored data for id: " + id + ", data: " + data);
}
public SmsData retrieveSmsData(Long id) {
SmsData data = codeCache.getIfPresent(id);
System.out.println("Retrieved data for id: " + id + ", data: " + data);
return data;
}
// 模拟数据类
static class SmsData {
int sendCount;
int checkCount;
public int getSendCount() { return sendCount; }
public void setSendCount(int sendCount) { this.sendCount = sendCount; }
public int getCheckCount() { return checkCount; }
public void setCheckCount(int checkCount) { this.checkCount = checkCount; }
@Override
public String toString() {
return "SmsData{sendCount=" + sendCount + ", checkCount=" + checkCount + '}';
}
}
public static void main(String[] args) throws InterruptedException {
// 现在即使创建多个MyCacheService实例,它们也共享同一个静态缓存
MyCacheService service1 = new MyCacheService();
MyCacheService service2 = new MyCacheService();
Long testId = 123L;
service1.storeSmsData(testId, 1);
// 现在从任何实例获取都应该成功
SmsData retrievedData = service2.retrieveSmsData(testId);
if (retrievedData == null) {
System.out.println("Error: Data for id " + testId + " was null!");
} else {
System.out.println("Success: Data for id " + testId + " retrieved: " + retrievedData);
}
}
}通过上述修改,codeCache现在是一个应用程序级别的、强引用的缓存,其存储的值将按照expireAfterWrite(24, TimeUnit.HOURS)的策略进行过期,而不是被垃圾回收器随意清除。
Caffeine是一个高性能的本地缓存库,但其强大的配置选项也需要开发者深入理解才能正确使用。当遇到Caffeine缓存值存储后无法获取的问题时,首要检查的便是缓存实例的作用域(是否为static final)以及是否错误地使用了weakKeys()或weakValues()。通过将应用程序级缓存声明为static final并移除不必要的弱引用配置,可以确保缓存数据按照预期持久化,从而构建稳定可靠的缓存系统。
以上就是Caffeine缓存值存储失效问题解析与最佳实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号