
本文旨在解决caffeine缓存中值存储后无法正确获取(返回null)的%ignore_a_1%。通过深入分析`weakkeys()`、`weakvalues()`以及缓存实例的作用域,文章揭示了导致值失效的核心原因,并提供了将缓存声明为`static final`并移除弱引用配置的解决方案。教程将详细阐述其原理,并给出示例代码,帮助开发者构建稳定可靠的caffeine缓存。
理解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 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。这通常是由两个主要因素导致的:弱引用配置和缓存实例的生命周期。
弱引用(Weak References)的陷阱
Caffeine提供了weakKeys()和weakValues()方法,允许缓存使用弱引用来持有键和值。在Java中,弱引用是一种特殊的引用类型,它不会阻止垃圾收集器回收其引用的对象。这意味着,如果一个对象只被弱引用所引用,并且没有其他强引用指向它,那么垃圾收集器在下一次运行时就会回收这个对象。
weakKeys(): 如果键只被缓存弱引用,并且没有其他强引用指向该键对象,那么该键及其对应的值可能会被垃圾回收。weakValues(): 如果值只被缓存弱引用,并且没有其他强引用指向该值对象,那么该值可能会被垃圾回收。
对于大多数缓存场景,我们期望缓存能够“强”持有其存储的键和值,直到它们因过期策略(如expireAfterWrite)或容量限制而被主动驱逐。使用弱引用通常是为了实现内存敏感的缓存,例如,当缓存的目的是作为其他地方已经强引用的对象的“影子”副本,或者你希望当内存紧张时,缓存能够自动释放那些不再被应用程序其他部分使用的对象。然而,如果不理解其含义,这会导致缓存行为与预期不符。
缓存实例的生命周期
如果Cache实例本身是一个普通的对象字段(如上述示例中的private Cache codeCache),那么它会随着其所在对象的生命周期而存在。如果包含Cache的MyCacheService对象在应用程序中被频繁创建和销毁,或者该对象本身被垃圾回收,那么其内部的Cache实例也会随之消失,导致所有存储的数据丢失。
对于一个应用程序级别的缓存,我们通常希望它在应用程序的整个生命周期内都保持活跃,并且其内部数据不会因为缓存实例本身被回收而丢失。
解决方案:static final与移除弱引用
解决上述问题的方法相对直接:确保缓存实例的生命周期与应用程序保持一致,并移除不必要的弱引用配置。
存了个图
视频图片解析/字幕/剪辑,视频高清保存/图片源图提取
17 查看详情
1. 将缓存声明为 static final
将Cache实例声明为static final具有以下优点:
静态(static): 确保codeCache是类级别的,而不是实例级别的。这意味着无论创建多少个MyCacheService对象,都只有一个codeCache实例。这对于应用程序范围的缓存至关重要。最终(final): 确保codeCache引用一旦初始化后就不会再改变。这增强了代码的健壮性和可预测性。
通过这种方式,codeCache实例将伴随应用程序的整个生命周期,直到应用程序终止,从而避免了缓存实例本身被垃圾回收的问题。
2. 移除 weakKeys() 和 weakValues()
除非有明确的、经过深思熟虑的理由需要弱引用行为,否则应移除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 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)的策略进行过期,而不是被垃圾回收器随意清除。
最佳实践与注意事项
缓存作用域的选择:应用程序级缓存:对于需要在整个应用程序生命周期内共享和持久化的数据,使用static final声明缓存是最佳实践。请求级/会话级缓存:如果缓存仅用于特定请求或会话的短暂生命周期,则可以将其作为实例字段,但需确保其生命周期管理得当,避免内存泄漏或过早回收。弱引用的适用场景:内存敏感缓存:当缓存的对象同时在应用程序的其他地方被强引用,并且你希望在内存紧张时,缓存能够自动释放这些对象,而无需显式清除时,可以考虑使用弱引用。例如,缓存对大型图片或计算结果的引用,这些图片或结果可能在其他地方有强引用。避免内存泄漏:在某些复杂的场景中,弱引用可以帮助打破循环引用,从而防止内存泄漏。重要提示:在决定使用weakKeys()或weakValues()之前,请务必充分理解其对缓存行为和垃圾回收的影响。对于大多数常规数据缓存,强引用是更安全和可预测的选择。Caffeine的线程安全性:Caffeine缓存是线程安全的,因此无需额外的同步机制即可在多线程环境中安全使用。过期策略与容量限制:除了本教程讨论的弱引用问题,还应根据业务需求合理配置缓存的过期策略(expireAfterWrite、expireAfterAccess)和容量限制(maximumSize),以有效管理内存和数据的新鲜度。缓存穿透与雪崩:在设计缓存时,还需考虑缓存穿透(查询不存在的数据导致每次都回源)、缓存击穿(热点数据失效导致大量请求回源)和缓存雪崩(大量缓存同时失效导致系统崩溃)等问题,并采取相应的策略(如布隆过滤器、热点数据永不过期、错峰过期等)进行防御。
总结
Caffeine是一个高性能的本地缓存库,但其强大的配置选项也需要开发者深入理解才能正确使用。当遇到Caffeine缓存值存储后无法获取的问题时,首要检查的便是缓存实例的作用域(是否为static final)以及是否错误地使用了weakKeys()或weakValues()。通过将应用程序级缓存声明为static final并移除不必要的弱引用配置,可以确保缓存数据按照预期持久化,从而构建稳定可靠的缓存系统。
以上就是Caffeine缓存值存储失效问题解析与最佳实践的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/571594.html
微信扫一扫
支付宝扫一扫