在 Java 中,使用 Map
存储内存数据并定时持久化到磁盘,是很多系统常见的需求,如缓存、配置中心、本地任务队列等。为了保证高性能和低延迟,选择一个高效的序列化框架至关重要。本文将介绍如何使用 Kryo 高效、线程安全地实现 Map
的持久化与恢复。
🔧 业务背景
我们需要定期将内存中的 Map<K, V>
写入磁盘文件,并支持随时从文件中读取恢复。要求包括:
- 高性能序列化,支持复杂对象
- 线程安全处理,避免并发问题
- 序列化过程不中断业务写操作
- 支持任意 Java 对象作为值
🚀 为什么选择 Kryo?
Kryo 是一个高性能、紧凑的 Java 序列化框架,优势明显:
特性 | Kryo 描述 |
---|---|
性能 | 比 Java 默认序列化快 10 倍以上 |
数据体积 | 序列化结果更小 |
支持复杂对象结构 | 支持对象图、循环引用等 |
可定制 | 支持注册类型、自定义 Serializer |
🧩 Maven 依赖
在 pom.xml
中添加 Kryo 及其依赖:
<dependencies> <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>5.5.0</version> </dependency> <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>minlog</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.objenesis</groupId> <artifactId>objenesis</artifactId> <version>3.3</version> </dependency> </dependencies>
⚠️ Kryo 线程安全问题
Kryo 实例不是线程安全的。如果多个线程同时调用同一个 Kryo
实例,会导致各种异常。
✅ 正确方式:每线程一个 Kryo 实例
使用 ThreadLocal<Kryo>
是最简单有效的做法:
private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> { Kryo kryo = new Kryo(); kryo.setRegistrationRequired(false); // 支持任意对象 kryo.setReferences(true); // 支持循环引用 return kryo; }); public static Kryo getKryo() { return kryoThreadLocal.get(); }
🧱 并发修改 Map 的问题
如果序列化过程中 Map
正在被其他线程修改,会出现:
- 抛出
ConcurrentModificationException
- 序列化结果不一致
- 部分数据丢失
✅ 推荐做法:加锁或快照
方式一:使用 ReadWriteLock
(强一致性)
private final Map<String, Object> map = new HashMap<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); public void serialize() { lock.readLock().lock(); try (Output output = new Output(new FileOutputStream("data.bin"))) { getKryo().writeObject(output, map); } catch (IOException e) { e.printStackTrace(); } finally { lock.readLock().unlock(); } }
方式二:快照复制(弱一致,性能优)
public void serializeWithSnapshot() { Map<String, Object> snapshot; synchronized (map) { snapshot = new HashMap<>(map); // 拷贝快照 } try (Output output = new Output(new FileOutputStream("data.bin"))) { getKryo().writeObject(output, snapshot); } }
适合读多写少场景,避免长时间读锁。
💾 持久化示例代码
public void saveToDisk(String path, Map<String, Object> map) { try (Output output = new Output(new FileOutputStream(path))) { getKryo().writeObject(output, map); } catch (IOException e) { e.printStackTrace(); } } public Map<String, Object> loadFromDisk(String path) { try (Input input = new Input(new FileInputStream(path))) { return getKryo().readObject(input, HashMap.class); } catch (IOException e) { e.printStackTrace(); return Collections.emptyMap(); } }
✅ 总结
项目 | 建议做法 |
---|---|
Kryo 实例线程安全 | 使用 ThreadLocal<Kryo> |
Map 并发访问 | 加锁或快照拷贝 |
持久化频率 | 可配合定时任务实现 |
Map 类型 | ConcurrentHashMap + 快照 最优 |
🧰 可拓展建议
- 可以封装成
PersistentMap<K, V>
,支持定时保存与自动恢复 - 可以集成进 Spring Boot,通过配置开启持久化
- 序列化格式可压缩(GZIP)进一步减小磁盘占用