Hutool 的 `TimedCache` 到期会自动清理吗? ——————hutool cache的"惰性清理"和"定期清理"
<p><strong>是的,Hutool 的 <code>TimedCache</code> 会在到期时自动清理缓存项,<em>但</em>它的清理机制是基于"惰性清理"和"定期清理"两种策略结合,而不是立即清理。</strong></p><h2 id="-核心清理机制">🎯 核心清理机制</h2>
<h3 id="1-惰性清理lazy-eviction">1. <strong>惰性清理(Lazy Eviction)</strong></h3>
<pre><code class="language-java">TimedCache<String, Object> cache = new TimedCache<>(1000); // 默认过期时间1秒
cache.put("key1", "value1");
// 在get时检查是否过期
Object value = cache.get("key1"); // 如果已过期,返回null并从map中移除
</code></pre>
<p>每次调用 <code>get()</code> 或 <code>containsKey()</code> 时,会检查该键是否过期,如果过期则移除。</p>
<h3 id="2-定期清理schedule-prune">2. <strong>定期清理(Schedule Prune)</strong></h3>
<pre><code class="language-java">TimedCache<String, Object> cache = new TimedCache<>(1000);
cache.schedulePrune(1000); // 启动定时清理,每1000ms执行一次
</code></pre>
<p>通过调用 <code>schedulePrune()</code> 方法启动定时清理任务,定期扫描并移除过期项。</p>
<h2 id="-使用示例">🔧 使用示例</h2>
<h3 id="示例1基本使用不自动清理">示例1:基本使用(不自动清理)</h3>
<pre><code class="language-java">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
}
}
</code></pre>
<h3 id="示例2启动定时清理">示例2:启动定时清理</h3>
<pre><code class="language-java">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();
}
}
</code></pre>
<h2 id="-清理时机总结">⚡ 清理时机总结</h2>
<table>
<thead>
<tr>
<th>清理类型</th>
<th>触发条件</th>
<th>清理范围</th>
<th>是否立即生效</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>惰性清理</strong></td>
<td>调用 <code>get()</code>、<code>containsKey()</code> 等方法</td>
<td>只检查被访问的key</td>
<td>是</td>
</tr>
<tr>
<td><strong>定时清理</strong></td>
<td>定时任务触发(需手动调用 <code>schedulePrune()</code>)</td>
<td>所有过期key</td>
<td>是</td>
</tr>
<tr>
<td><strong>容量清理</strong></td>
<td>达到缓存容量上限</td>
<td>最近最少使用的过期项</td>
<td>是</td>
</tr>
</tbody>
</table>
<h2 id="-源码解析">📊 源码解析</h2>
<p>查看 <code>TimedCache</code> 源码的关键方法:</p>
<h3 id="1-检查过期并移除的方法">1. <strong>检查过期并移除的方法</strong></h3>
<pre><code class="language-java">// 在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;
}
// ... 返回缓存值
}
</code></pre>
<h3 id="2-定时清理任务">2. <strong>定时清理任务</strong></h3>
<pre><code class="language-java">public void schedulePrune(long delay) {
this.pruneTimer.schedule(new TimerTask() {
@Override
public void run() {
pruneCache(); // 执行清理
}
}, delay, delay);
}
</code></pre>
<h2 id="-使用建议">💡 使用建议</h2>
<h3 id="1-根据场景选择清理策略">1. <strong>根据场景选择清理策略</strong></h3>
<pre><code class="language-java">// 场景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);
</code></pre>
<h3 id="2-合理设置过期时间">2. <strong>合理设置过期时间</strong></h3>
<pre><code class="language-java">// 为不同数据设置不同过期时间
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秒
</code></pre>
<h3 id="3-监听器支持">3. <strong>监听器支持</strong></h3>
<pre><code class="language-java">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);
}
});
</code></pre>
<h2 id="️-注意事项">⚠️ 注意事项</h2>
<ol>
<li><strong>定时清理需手动启动</strong>:<code>schedulePrune()</code> 必须显式调用才会启动定时清理</li>
<li><strong>内存泄漏风险</strong>:如果不调用 <code>schedulePrune()</code> 且从不访问过期key,这些key会一直占用内存</li>
<li><strong>Timer的内存泄漏</strong>:定时清理使用 <code>Timer</code>,如果缓存对象被GC,定时任务不会自动取消</li>
<li><strong>性能考虑</strong>:定时清理间隔不宜过短,特别是缓存项很多时</li>
</ol>
<h2 id="-完整生命周期示例">🔄 完整生命周期示例</h2>
<pre><code class="language-java">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();
}
}
</code></pre>
<h2 id="-总结">🎯 总结</h2>
<p><strong><code>TimedCache</code> 的自动清理机制</strong>:</p>
<ol>
<li><strong>不会立即自动清理</strong>:默认只有惰性清理</li>
<li><strong>需要手动启动定时清理</strong>:通过 <code>schedulePrune()</code> 方法</li>
<li><strong>清理策略可配置</strong>:可以根据业务需求选择清理频率</li>
</ol>
<p><strong>最佳实践</strong>:在创建 <code>TimedCache</code> 后立即调用 <code>schedulePrune()</code>,以确保过期数据会被及时清理,避免内存泄漏问题。</p>
<pre><code class="language-java">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));
}
...
}
</code></pre>
<p><br><br><br><br></p><hr><p></p>
<p><em>ref:缓存过期的惰性删除(Lazy Deletion)策略</em><br>
<em>ref:hutool cache的"惰性清理"和"定期清理"</em><br>
<em>ref:如何精准控制业务合同到期后不再发生业务?</em></p>
</div>
<div id="MySignature" role="contentinfo">
<hr class="signhr"><p style="text-indent:2em;font-size:12px;text-align:center">当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge<br>本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/19759976</p><hr class="signhr">
<style>hr.signhr{width:80%;margin:0 auto;border: 0;height: 4px;background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0))}</style><br><br>
来源:https://www.cnblogs.com/buguge/p/19759976
頁:
[1]