1. 简介
MapMaker
是 Guava 提供的一个构建器类,用于简单粗暴地创建线程安全的 Map。
Java 标准库中虽然提供了 WeakHashMap
,支持对键(key)使用弱引用,但并没有原生支持对值(value)使用弱引用的 Map 实现。✅
而 MapMaker
正好弥补了这一短板——它通过简洁的 Builder 模式,支持为 key 和 value 分别配置弱引用,极大简化了高级缓存场景的实现。⚠️
本文将带你掌握 MapMaker
的核心用法,特别是如何利用弱引用构建高效、防内存泄漏的缓存结构。
2. Maven 依赖
首先引入 Guava 依赖,当前最新稳定版已包含 MapMaker
(注意:在较新版本中部分 API 已标记为过时,但仍可用):
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
⚠️ 注意:从 Guava 12 开始,
MapMaker
替代了旧的MapMakerInternalMap
,并在后续版本中逐步演进。本文基于主流稳定版本讲解。
3. 缓存场景示例
假设我们正在开发一个服务端应用,需要维护两种用户缓存:
- 会话缓存(session cache):用户登出或超时后应自动失效
- 用户档案缓存(profile cache):仅当用户更新档案时才失效,生命周期较长
理想情况下,我们希望:
- 当
User
对象被 GC 回收时,对应的 session 缓存条目自动清除(key 使用弱引用) - 当
Profile
对象被 GC 回收时,profile 缓存中的 value 自动释放(value 使用弱引用)
这正是 MapMaker
的典型用武之地。
3.1 数据结构定义
先定义三个简单的 POJO 类:
public class User {
private long id;
private String name;
public User(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
}
public class Session {
private long id;
public Session(long id) {
this.id = id;
}
public long getId() {
return id;
}
}
public class Profile {
private long id;
private String type;
public Profile(long id, String type) {
this.id = id;
this.type = type;
}
public long getId() {
return id;
}
public String getName() {
return type;
}
}
3.2 创建缓存实例
使用 MapMaker
创建线程安全的 ConcurrentMap
:
ConcurrentMap<User, Session> sessionCache = new MapMaker().makeMap();
✅ 该 Map 不允许 key 或 value 为 null,避免了潜在的 NPE 风险。
同样创建 profile 缓存:
ConcurrentMap<User, Profile> profileCache = new MapMaker().makeMap();
📌 默认情况下,MapMaker
创建的 Map 初始容量为 16。若需调整,可通过 initialCapacity()
设置:
ConcurrentMap<User, Profile> profileCache = new MapMaker()
.initialCapacity(100)
.makeMap();
3.3 调整并发级别
MapMaker
默认的并发级别(concurrencyLevel)是 4,意味着最多支持 4 个线程并发写入。
但在高并发场景下(如 session cache),可能需要更高并发支持:
ConcurrentMap<User, Session> sessionCache = new MapMaker()
.concurrencyLevel(10)
.makeMap();
📌 这个值会影响内部 segment 分段锁的数量,合理设置可减少线程竞争。一般设置为预期并发写线程数。
3.4 使用弱引用
默认情况下,Map 中的 key 和 value 都是强引用,即使外部不再引用它们,Map 仍会持有,容易导致内存泄漏。❌
✅ 场景一:key 使用弱引用(会话缓存)
当 User
被 GC 后,希望 session 缓存自动失效:
ConcurrentMap<User, Session> sessionCache = new MapMaker()
.weakKeys()
.makeMap();
此时,一旦 User
对象不可达,其对应的 entry 将在下一次读/写操作时被自动清理。
✅ 场景二:value 使用弱引用(档案缓存)
Profile
可能较大,希望在无引用时自动释放:
ConcurrentMap<User, Profile> profileCache = new MapMaker()
.weakValues()
.makeMap();
这样,当外部不再持有 Profile
实例时,GC 可回收它,Map 中对应 entry 也会被清理。
⚠️ 注意:
size()
方法返回的大小可能不准确,因为它不会立即感知到弱引用条目的回收。建议不要依赖size()
做精确判断。
4. MapMaker 内部原理
MapMaker
的底层实现会根据配置动态选择:
配置情况 | 实际生成的 Map 类型 |
---|---|
未启用弱引用 | ConcurrentHashMap |
启用 weakKeys 或 weakValues | 自定义的 MapMakerInternalMap |
📌 关键差异:
- 普通情况:使用
equals()
和hashCode()
判断 key 相等性 - 启用弱引用后:改用 引用相等性(identity equality),即
==
和System.identityHashCode()
这意味着:即使两个对象 equals()
返回 true,只要它们不是同一个实例,就会被视为不同的 key。⚠️
💡 这是为了避免弱引用回收过程中出现状态不一致的问题,属于设计取舍。
5. 总结
MapMaker
虽然在新版本 Guava 中部分 API 已被标记为过时(推荐使用 CacheBuilder
替代),但在一些轻量级、高性能的场景中依然有其价值。
本文重点总结:
✅ 支持 weakKeys()
和 weakValues()
,解决标准库无法对 value 使用弱引用的问题
✅ 可设置 initialCapacity
和 concurrencyLevel
,优化性能
✅ 自动生成线程安全的 ConcurrentMap
,开箱即用
⚠️ 启用弱引用后使用 identity 比较,需注意语义变化
❌ size()
方法在弱引用场景下可能不准,避免用于精确控制
📌 实际项目中,若涉及复杂缓存策略(如 TTL、 maxSize、自动加载),建议优先考虑
CacheBuilder
。但对于简单场景,MapMaker
依然是个不错的“轻骑兵”。
完整示例代码已托管至 GitHub: https://github.com/baomidou/tutorials/tree/master/guava-modules/guava-collections-map