登录
首页 >  文章 >  java教程

Java简单缓存实现方法解析

时间:2026-01-05 09:32:34 428浏览 收藏

学习知识要善于思考,思考,再思考!今天golang学习网小编就给大家带来《Java实现简单缓存机制详解》,以下内容主要包含等知识点,如果你正在学习或准备学习文章,就都不要错过本文啦~让我们一起来看看吧,能帮助到你就更好了!

用 HashMap 做缓存会引发内存溢出、并发异常和数据过期问题;ConcurrentHashMap 仅解决线程安全,仍需手动实现过期与驱逐;复杂场景应直接使用 Caffeine 等成熟缓存库。

在Java里实现简单缓存机制_JavaMap缓存项目说明

HashMap 做缓存会出什么问题?

直接用 HashMap 存缓存数据,短期看能跑通,但很快会遇到三类典型问题:内存持续增长导致 OutOfMemoryError;多线程并发读写引发 ConcurrentModificationException 或脏数据;没有过期机制,旧数据永远不更新。这不是“能不能用”的问题,而是“什么时候崩”的问题。

为什么选 ConcurrentHashMap 而不是 HashMap

ConcurrentHashMap 是线程安全的,支持高并发读(无锁)、分段写(JDK 8 后为 CAS + synchronized 细粒度锁),比 Collections.synchronizedMap() 性能好得多。但它仍不解决过期和容量控制——它只是缓存的“容器底座”,不是完整方案。

常见误用:
- 把 ConcurrentHashMap 当成带过期功能的缓存(它不支持 TTL)
- 在循环中直接调用 put() + get() 而不处理 compute-if-absent 竞态(推荐用 computeIfAbsent()

手动实现带过期和容量限制的简易缓存

核心逻辑就三点:时间戳标记、LRU 驱逐、读写线程安全。下面是一个可直接运行的轻量实现:

public class SimpleCache<K, V> {
    private final ConcurrentHashMap<K, CacheEntry<V>> cache;
    private final long expireMillis;
    private final int maxCapacity;
<pre class="brush:java;toolbar:false;">public SimpleCache(int maxCapacity, long expireMillis) {
    this.cache = new ConcurrentHashMap&lt;&gt;();
    this.maxCapacity = maxCapacity;
    this.expireMillis = expireMillis;
}

public V get(K key) {
    CacheEntry&lt;V&gt; entry = cache.get(key);
    if (entry == null || System.currentTimeMillis() - entry.timestamp &gt; expireMillis) {
        cache.remove(key);
        return null;
    }
    return entry.value;
}

public V put(K key, V value) {
    if (cache.size() &gt;= maxCapacity &amp;&amp; !cache.containsKey(key)) {
        // 简单驱逐:随机移除一个(生产建议换 LRU 链表)
        K toRemove = cache.keySet().stream().findAny().orElse(null);
        if (toRemove != null) cache.remove(toRemove);
    }
    cache.put(key, new CacheEntry&lt;&gt;(value));
    return value;
}

private static class CacheEntry&lt;V&gt; {
    final V value;
    final long timestamp;

    CacheEntry(V value) {
        this.value = value;
        this.timestamp = System.currentTimeMillis();
    }
}

}

说明:
- 过期判断在 get() 中完成,避免后台线程开销
- 驱逐策略用的是简单随机剔除,真实项目应替换为基于访问顺序的 LRU(比如用 LinkedHashMap + removeEldestEntry,但要注意线程安全)
- put() 不保证强一致性:若刚好达到 maxCapacity,多个线程同时写可能短暂超限

什么时候该放弃手写,改用成熟库?

一旦出现以下任一情况,手写缓存就该停了:
- 需要精确的 TTL / TTI(访问后重置过期时间)
- 要求缓存穿透/击穿防护(如空值缓存、互斥锁)
- 需与 Spring Boot 自动集成(@Cacheable
- 有堆外缓存、多级缓存、分布式同步需求

这时直接上 Caffeine:它默认启用 LRU + 过期 + 弱引用清理,API 简洁,性能碾压手写。例如一行代码就能构建带自动刷新的缓存:
Caffeine.newBuilder().maximumSize(10_000).expireAfterWrite(10, TimeUnit.MINUTES).build(key -> loadFromDB(key));

手写缓存的价值在于理解原理和应对极简场景;它的危险在于容易被当成“够用”而长期留在生产里——尤其当团队开始加“临时”过期逻辑、手动清理线程、双重检查锁时,就是重构信号。

理论要掌握,实操不能落!以上关于《Java简单缓存实现方法解析》的详细介绍,大家都掌握了吧!如果想要继续提升自己的能力,那么就来关注golang学习网公众号吧!

前往漫画官网入口并下载 ➜
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>