Java读写并发:Lock+Volatile与ReadWriteLock的性能及生产选型
读多写少场景下的并发方案对比:吞吐量与生产选型
问题背景
假设读操作的数量是写操作的10倍,从吞吐量角度来看,以下两种并发方案哪种更优?生产代码中应选用哪种方案?
方案1:写操作加锁,读操作使用volatile变量
import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public final class InstanceProvider { private final Lock wLock = new ReentrantLock(); private volatile List<Instance> instances; public void setInstances(List<Instance> newInstances) { wLock.lock(); try { doSmthWith(newInstances); instances = newInstances; } finally { wLock.unlock(); } } public List<Instance> getInstances() { return instances; } @Getter public static final class Instance { private final String name; public Instance(String name) { this.name = name; } } }
方案2:读写操作使用ReadWriteLock
import java.util.List; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public final class InstanceProvider { private final ReadWriteLock lock = new ReentrantReadWriteLock(); private List<Instance> instances; public void setInstances(List<Instance> newInstances) { lock.writeLock().lock(); try { doSmthWith(newInstances); instances = newInstances; } finally { lock.writeLock().unlock(); } } public List<Instance> getInstances() { lock.readLock().lock(); try { return instances; } finally { lock.readLock().unlock(); } } @Getter public static final class Instance { private final String name; public Instance(String name) { this.name = name; } } }
我的假设:读取volatile变量的成本更低(通常大部分时间会被缓存),而锁需要执行额外的指令。
吞吐量对比
从吞吐量角度看,方案1的表现会更优,原因如下:
- 方案1的读操作直接读取volatile变量,volatile的读操作开销极低:它仅保证变量的可见性,无需线程阻塞或复杂的锁指令,大部分情况下变量会被CPU缓存命中,几乎无额外开销。
- 方案2的读操作需要获取/释放读锁,尽管ReentrantReadWriteLock的读锁支持多线程并发持有,但加锁解锁过程仍涉及CAS操作、内存屏障等指令,在读操作是写操作10倍的读密集场景下,大量读锁操作的累积开销会显著高于volatile读,拉低整体吞吐量。
生产代码选型建议
生产环境中不能只看吞吐量,还要结合线程安全要求和业务场景:
优先选方案1的场景
如果满足以下条件,方案1是更优选择:
- 返回的
List<Instance>是不可变对象(比如调用setInstances时传入Collections.unmodifiableList包装后的列表,或者Instance本身是不可变类且列表不会被外部修改); - 业务仅需要保证
instances引用的可见性,无需在读操作期间阻止写操作(即允许读操作看到最新的写结果即可,不需要读操作期间数据绝对稳定)。
这种场景下,方案1既保证了线程安全,又能最大化吞吐量。
优先选方案2的场景
如果存在以下情况,方案2更合适:
- 返回的
List<Instance>可能被外部修改,或者需要在读操作期间保证数据的一致性(比如读操作过程中不允许写操作修改instances引用); - 业务需要更严格的线程安全保障,比如避免读操作读到半初始化的对象(虽然方案1中volatile也能避免这种情况,但读锁可以提供更强的互斥语义)。
内容的提问来源于stack exchange,提问作者tinyzero4
相关产品推荐
相关产品推荐

