buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

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);
    }
});

⚠️ 注意事项

  1. 定时清理需手动启动schedulePrune() 必须显式调用才会启动定时清理
  2. 内存泄漏风险:如果不调用 schedulePrune() 且从不访问过期key,这些key会一直占用内存
  3. Timer的内存泄漏:定时清理使用 Timer,如果缓存对象被GC,定时任务不会自动取消
  4. 性能考虑:定时清理间隔不宜过短,特别是缓存项很多时

🔄 完整生命周期示例

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 的自动清理机制

  1. 不会立即自动清理:默认只有惰性清理
  2. 需要手动启动定时清理:通过 schedulePrune() 方法
  3. 清理策略可配置:可以根据业务需求选择清理频率

最佳实践:在创建 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:如何精准控制业务合同到期后不再发生业务?

posted on 2026-03-23 21:04  buguge  阅读(1)  评论(0)    收藏  举报