Java类字段并发访问是否需要使用ReadWriteLock同步?
先直接给结论:当前代码必须加同步措施,但不一定非要用ReadWriteLock,不加的话会存在可见性问题,读线程可能永远看不到更新后的someField引用。
问题核心:Java内存模型的可见性规则
根据Java语言规范的内存模型部分,线程间的操作需要满足happens-before关系,才能保证一个线程的修改对另一个线程可见。
在你的代码里:
- 定时任务线程执行
someField = theNewValue()的写操作 - 其他线程调用
read()方法执行读操作
这两个操作之间没有任何同步机制来建立happens-before关系,所以读线程完全可能从自己的本地缓存中读取到旧的someField引用,永远看不到更新后的Map。
为什么有人说不需要同步?
大概率是误解了场景:比如误以为定时任务是单线程就不会有问题,但单线程写不代表读线程能看到最新值——可见性问题和线程数量无关,只和内存模型的规则有关。
可选的解决方案
1. 用volatile修饰字段(推荐,更轻量)
把someField声明为volatile:
private volatile Map<String, String> someField;
volatile关键字的作用就是保证:对该变量的写操作会立刻刷新到主内存,且后续的读操作会直接从主内存读取,自动建立了写操作到读操作的happens-before关系。
另外注意:如果theNewValue()返回的Map是不可变的(比如用Collections.unmodifiableMap包装,或者直接用Guava的ImmutableMap),那连Map内部的线程安全都不用考虑——因为一旦赋值后就不会修改Map的内容,只需要保证引用的可见性即可。
2. 用ReadWriteLock(可行,但没必要)
如果一定要用ReadWriteLock,代码会变成这样:
private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private Map<String, String> someField; public void someScheduledActivity() { rwLock.writeLock().lock(); try { someField = theNewValue(); } finally { rwLock.writeLock().unlock(); } } public String read(String id) { rwLock.readLock().lock(); try { return this.someField.getOrDefault(id, null); } finally { rwLock.readLock().unlock(); } }
但这种方式比volatile重,因为锁会带来额外的开销,而你的场景只是替换整个引用,用volatile足够解决问题。
官方文档依据
Java语言规范(JLS)中明确规定:如果没有同步机制,线程对变量的写入结果不能保证对其他线程可见。具体可参考JLS第17章(Java Memory Model)中关于happens-before的定义,以及volatile变量的内存语义。
内容的提问来源于stack exchange,提问作者Kaveh Shahbazian

