Java中如何保证LocalCache组合数据读取的原子性?
解决LocalCache多线程下状态一致性问题
当前代码的核心问题是:add方法虽加了同步,但读取size、keys、data的操作是分开执行的,没有原子性保障。比如foo()里先拿size,之后其他线程执行add修改了size、keys、data,再拿keys和data时,三者的状态就对应不上了。
下面给出几种实用的解决思路:
1. 统一同步所有读写操作
给getData()、getKeys()、getSize()都加上synchronized修饰,这样任何线程读取这三个字段时,都会持有LocalCache对象的锁,确保读取期间没有add操作执行,自然能保证三者状态一致。
修改后的示例:
public class LocalCache { int size; List<String> keys; float[] data; public LocalCache(int initialCapacity) { // 初始化data数组,避免扩容前数组越界 this.data = new float[initialCapacity * 256]; this.keys = new ArrayList<>(initialCapacity); this.size = 0; } public synchronized void add(String oneKey, float[] oneData) { // 校验数据长度,符合设计预期 if (oneData.length != 256) { throw new IllegalArgumentException("输入数据长度必须为256"); } // 动态扩容data数组 if (size * 256 + 256 > data.length) { this.data = Arrays.copyOf(data, data.length * 2); } System.arraycopy(oneData, 0, this.data, size * 256, 256); keys.add(oneKey); size++; } public synchronized float[] getData() { // 返回有效数据的拷贝,避免外部修改内部数组 return Arrays.copyOf(data, size * 256); } public synchronized List<String> getKeys() { // 返回拷贝,避免外部修改内部列表 return new ArrayList<>(keys); } public synchronized int getSize() { return size; } }
这种方式简单直接,但缺点是如果读取操作很频繁,会导致add操作被频繁阻塞,影响写入性能。
2. 提供原子性的快照获取方法
定义一个封装了size、keys、data的快照类,然后提供一个同步的方法一次性返回完整快照。这样foo()只需要调用一次方法就能拿到一致的状态,同时减少锁的持有时间(只在生成快照时持有锁)。
示例代码:
public class LocalCache { int size; List<String> keys; float[] data; public LocalCache(int initialCapacity) { this.data = new float[initialCapacity * 256]; this.keys = new ArrayList<>(initialCapacity); this.size = 0; } public synchronized void add(String oneKey, float[] oneData) { if (oneData.length != 256) { throw new IllegalArgumentException("输入数据长度必须为256"); } if (size * 256 + 256 > data.length) { this.data = Arrays.copyOf(data, data.length * 2); } System.arraycopy(oneData, 0, this.data, size * 256, 256); keys.add(oneKey); size++; } // 一次性获取一致的快照 public synchronized CacheSnapshot getSnapshot() { return new CacheSnapshot( size, new ArrayList<>(keys), Arrays.copyOf(data, size * 256) ); } // 内部快照类,封装一致的状态 public static class CacheSnapshot { public final int size; public final List<String> keys; public final float[] data; private CacheSnapshot(int size, List<String> keys, float[] data) { this.size = size; this.keys = keys; this.data = data; } } }
使用时foo()改成:
void foo() { LocalCache.CacheSnapshot snapshot = localCache.getSnapshot(); // 使用snapshot.size、snapshot.keys、snapshot.data,三者状态完全一致 }
这种方式更高效,因为生成快照后外部可以随意使用,不需要一直持有锁,同时返回的是拷贝,避免外部修改缓存内部状态。
额外注意点
- 原代码中data数组没有初始化,add时会直接抛出数组越界异常,必须在构造函数中初始化,或者在add方法中做动态扩容。
- 原add方法用for循环复制数据,效率不如
System.arraycopy,建议替换以提升性能。 - 返回数据时尽量返回拷贝,避免外部代码修改缓存内部的keys或data数组,导致状态混乱。
内容的提问来源于stack exchange,提问作者Tiina
相关产品推荐
相关产品推荐

