HashMap可作轻量级本地缓存底层,但需用computeIfAbsent避免竞态;高并发须换ConcurrentHashMap;适用短期、key可控、无过期需求场景;LRU可用LinkedHashMap定制。

HashMap 本身不带缓存机制,但它能作为轻量级本地缓存的底层容器——关键在于如何用对方法、避开线程安全和竞态问题。
用 computeIfAbsent 避免重复计算
这是最直接有效的方式,尤其适合单线程或低并发场景。它把“查缓存”和“没命中时计算并存入”合并成一步,天然防止重复执行耗时逻辑。
- 写法示例:cache.computeIfAbsent(key, k -> expensiveCalculation(k))
- 如果用传统方式先 get() 再判断是否为 null,再手动 put(),中间存在竞态窗口:两个线程可能同时发现 key 不存在,都去执行 expensiveCalculation,造成资源浪费甚至状态错乱
- 注意:computeIfAbsent 要求 mappingFunction 不能为 null,且内部不应依赖外部可变对象(比如非 final 的 StringBuilder 或共享计数器)
高并发下必须换 ConcurrentHashMap
HashMap 是线程不安全的。多线程共用一个 HashMap 缓存,即使加了 synchronized 方法锁,也会严重串行化,吞吐反而下降。
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 推荐直接使用 ConcurrentHashMap,它对不同桶加锁,支持更高并发度
- 仍优先用 computeIfAbsent,它的原子性在 ConcurrentHashMap 中由 synchronized + CAS 保障,比手写双重检查更可靠
- 避免用 Collections.synchronizedMap 包装 HashMap —— 它只是给整个 map 加一把全局锁,性能瓶颈明显
注意缓存的适用边界
HashMap / ConcurrentHashMap 做缓存,只适合特定类型的数据:
立即学习“Java免费学习笔记(深入)”;
- 短期存在:比如类元信息、配置项、模板解析结果,生命周期与应用一致
- key 空间可控:不会无限增长,否则内存持续上涨
- 无需自动过期或淘汰:它不提供 TTL、LRU、LFU 等能力;需要这些功能得上 Caffeine、Guava Cache 或 Redis
- value 构建开销确实大:比如远程调用、JSON 反序列化、复杂对象组装;如果只是简单算术,缓存收益远小于哈希计算成本
简单 LRU 缓存可用 LinkedHashMap
如果需要按访问顺序淘汰老数据(比如保留最近 100 个查询结果),可以基于 LinkedHashMap 自定义:
- 构造时传入 true 启用 accessOrder 模式:new LinkedHashMap(16, 0.75f, true)
- 重写 removeEldestEntry 方法,当 size() 超过阈值就返回 true,触发自动删除最老项
- 注意:它仍是线程不安全的,高并发需配合 synchronized 或封装在无锁结构中

















