用hashmap缓存实体类需关注key设计、线程安全与生命周期管理:key应基于业务唯一标识而非实体对象本身;多线程场景必须用concurrenthashmap或同步包装;需主动管理过期、更新与深拷贝,避免脏数据、内存泄漏和并发异常。

用 HashMap 缓存实体类,本质是把对象当 value 存,关键在 key 的设计、线程安全、生命周期管理这三块。不是简单 new 个 HashMap.put(key, entity) 就完事,否则容易出并发异常、内存泄漏或查不到数据。
选对 key:别直接用实体类本身做 key
实体类默认的 hashCode 和 equals 是基于内存地址的,两个内容相同但不同实例的对象,会被当成不同 key。比如两次查数据库生成的 User 对象,字段一模一样,但 put 进去算两次,get 也取不到。
- 推荐用业务唯一标识字段组合成 key,比如 "user:" + userId、"order:" + orderId + ":detail"
- 如果 key 逻辑复杂,封装成一个轻量级 key 类,重写 hashCode() 和 equals(),只比关键字段(如 id、code)
- 避免用整个实体对象作 key —— 除非你明确重写了这两个方法,且保证不可变
线程安全不能靠运气
普通 HashMap 在多线程读写时可能死循环或数据丢失,尤其在 put 触发扩容时。业务缓存基本都跑在 Web 容器或并发环境下,必须处理。
- 高并发读多写少场景,优先用 ConcurrentHashMap,它分段锁/ CAS 优化,性能好又安全
- 如果只是偶尔更新、读为主,可用 Collections.synchronizedMap(new HashMap()),但要注意:迭代时仍需手动同步,否则可能抛 ConcurrentModificationException
- 不要自己加 synchronized 块包住整个 map 操作——锁粒度太大,会严重拖慢吞吐
实体缓存要管“活期”和“定期”
实体类通常有状态、有时效,比如用户信息变了,缓存不更新就变成脏数据;或者订单详情查一次后几小时都不变,没必要一直占着内存。
- 加简单过期机制:自己维护一个 ConcurrentHashMap
> ,CacheEntry 包含 value + expireTime - 查的时候先判断是否过期,过期则 remove 并触发重新加载(可异步)
- 更省心的做法是引入 Caffeine 或 Guava Cache,它们原生支持自动刷新、最大容量、过期策略,比手写健壮得多
注意序列化与深浅拷贝隐患
如果后续要落盘、跨进程传输,或缓存里存的是含可变集合的实体(比如 User 里有个 List
- put 进去前,确认是否需要深拷贝——特别是从数据库查询结果直接塞进去时,避免外部修改影响缓存内容
- 如果用了 JSON 序列化做缓存(如 Redis),那实体类得有无参构造、getter/setter,且字段不能是 final 或私有未暴露
- 本地缓存一般不用序列化,但要注意:缓存中的对象和业务代码里的可能是同一份引用,改了缓存里的,其他地方也跟着变
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











