Hutool 的 `TimedCache` 到期会自动清理吗? ——————hutool cache的"惰性清理"和"定期清理"
是的,Hutool 的 TimedCache 会在到期时自动清理缓存项,但它的清理机制是基于"惰性清理"和"定期清理"两种策略结合,而不是立即清理。
🎯 核心清理机制
1. 惰性清理(Lazy Eviction)
TimedCache<String, Object> cache = new TimedCache<>(1000); // 默认过期时间1秒
cache.put("key1", "value1");
// 在get时检查是否过期
Object value = cache.get("key1"); // 如果已过期,返回null并从map中移除
每次调用 get() 或 containsKey() 时,会检查该键是否过期,如果过期则移除。
2. 定期清理(Schedule Prune)
TimedCache<String, Object> cache = new TimedCache<>(1000);
cache.schedulePrune(1000); // 启动定时清理,每1000ms执行一次
通过调用 schedulePrune() 方法启动定时清理任务,定期扫描并移除过期项。
🔧 使用示例
示例1:基本使用(不自动清理)
import cn.hutool.cache.impl.TimedCache;
public class TimedCacheExample1 {
public static void main(String[] args) throws InterruptedException {
// 创建缓存,默认过期时间1000ms
TimedCache<String, String> cache = new TimedCache<>(1000);
// 放入数据
cache.put("key1", "value1");
// 立即获取
System.out.println("立即获取:" + cache.get("key1")); // 输出: value1
// 等待1.1秒
Thread.sleep(1100);
// 过期后获取
System.out.println("1.1秒后获取:" + cache.get("key1")); // 输出: null
// 注意:此时key1会从map中移除
// 检查大小
System.out.println("缓存大小:" + cache.size()); // 输出: 0
}
}
示例2:启动定时清理
import cn.hutool.cache.impl.TimedCache;
public class TimedCacheExample2 {
public static void main(String[] args) throws InterruptedException {
// 创建缓存,默认过期时间1000ms
TimedCache<String, String> cache = new TimedCache<>(1000);
// 启动定时清理,每500ms执行一次
cache.schedulePrune(500);
cache.put("key1", "value1");
cache.put("key2", "value2", 2000); // 单独设置过期时间2000ms
System.out.println("初始大小:" + cache.size()); // 输出: 2
Thread.sleep(1100);
System.out.println("1.1秒后大小:" + cache.size()); // 输出: 1(key1被清理)
Thread.sleep(1000);
System.out.println("2.1秒后大小:" + cache.size()); // 输出: 0(key2被清理)
// 关闭定时清理
cache.cancelPruneSchedule();
}
}
⚡ 清理时机总结
| 清理类型 | 触发条件 | 清理范围 | 是否立即生效 |
|---|---|---|---|
| 惰性清理 | 调用 get()、containsKey() 等方法 |
只检查被访问的key | 是 |
| 定时清理 | 定时任务触发(需手动调用 schedulePrune()) |
所有过期key | 是 |
| 容量清理 | 达到缓存容量上限 | 最近最少使用的过期项 | 是 |
📊 源码解析
查看 TimedCache 源码的关键方法:
1. 检查过期并移除的方法
// 在get操作时会调用
public V get(K key, boolean isUpdateLastAccess) {
CacheObj<K, V> co = getWithoutLock(key);
if (co == null) {
missCount.increment();
return null;
}
// 检查是否过期
if (co.isExpired()) {
// 过期则移除
remove(key, true);
missCount.increment();
return null;
}
// ... 返回缓存值
}
2. 定时清理任务
public void schedulePrune(long delay) {
this.pruneTimer.schedule(new TimerTask() {
@Override
public void run() {
pruneCache(); // 执行清理
}
}, delay, delay);
}
💡 使用建议
1. 根据场景选择清理策略
// 场景1:低频率访问,使用惰性清理即可
TimedCache<String, Object> cache1 = new TimedCache<>(5000);
// 场景2:高频率访问,需要定时清理避免内存泄漏
TimedCache<String, Object> cache2 = new TimedCache<>(5000);
cache2.schedulePrune(1000); // 每1秒清理一次
// 场景3:需要严格内存控制
TimedCache<String, Object> cache3 = new TimedCache<>(5000, 1000); // 容量限制1000
cache3.schedulePrune(500);
2. 合理设置过期时间
// 为不同数据设置不同过期时间
TimedCache<String, Object> cache = new TimedCache<>();
// 默认过期时间5秒
cache.setDefaultTimeout(5000);
// 特定key设置特定过期时间
cache.put("session_token", "abc123", 30 * 60 * 1000); // 30分钟
cache.put("captcha", "1234", 5 * 60 * 1000); // 5分钟
cache.put("page_cache", "<html>...</html>", 1000); // 1秒
3. 监听器支持
TimedCache<String, Object> cache = new TimedCache<>(1000);
cache.setListener(new CacheListener<String, Object>() {
@Override
public void onRemove(String key, Object value) {
System.out.println("Key被移除:" + key + ",值:" + value);
}
});
⚠️ 注意事项
- 定时清理需手动启动:
schedulePrune()必须显式调用才会启动定时清理 - 内存泄漏风险:如果不调用
schedulePrune()且从不访问过期key,这些key会一直占用内存 - Timer的内存泄漏:定时清理使用
Timer,如果缓存对象被GC,定时任务不会自动取消 - 性能考虑:定时清理间隔不宜过短,特别是缓存项很多时
🔄 完整生命周期示例
import cn.hutool.cache.impl.TimedCache;
public class TimedCacheLifecycle {
public static void main(String[] args) throws InterruptedException {
// 1. 创建缓存,默认过期时间2秒
TimedCache<String, String> cache = new TimedCache<>(2000);
// 2. 启动定时清理,每1秒一次
cache.schedulePrune(1000);
// 3. 添加数据
cache.put("short", "短期数据", 1000); // 1秒过期
cache.put("medium", "中期数据", 3000); // 3秒过期
cache.put("long", "长期数据"); // 默认2秒过期
// 4. 监控状态
for (int i = 0; i < 5; i++) {
System.out.println("\n=== 第 " + (i+1) + " 秒 ===");
System.out.println("缓存大小: " + cache.size());
System.out.println("short: " + cache.get("short"));
System.out.println("medium: " + cache.get("medium"));
System.out.println("long: " + cache.get("long"));
Thread.sleep(1000);
}
// 5. 清理资源
cache.clear();
cache.cancelPruneSchedule();
}
}
🎯 总结
TimedCache 的自动清理机制:
- 不会立即自动清理:默认只有惰性清理
- 需要手动启动定时清理:通过
schedulePrune()方法 - 清理策略可配置:可以根据业务需求选择清理频率
最佳实践:在创建 TimedCache 后立即调用 schedulePrune(),以确保过期数据会被及时清理,避免内存泄漏问题。
public final class LocalCacheUtil {
private static TimedCache<String, Object> timedCache = new TimedCache<>(DAYS.toMillis(1));
static {
// init prune schedule at fix rate
timedCache.schedulePrune(MINUTES.toMillis(3));
}
...
}
ref:缓存过期的惰性删除(Lazy Deletion)策略
ref:hutool cache的"惰性清理"和"定期清理"
ref:如何精准控制业务合同到期后不再发生业务?
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/19759976
浙公网安备 33010602011771号