多线程传递无volatile/同步方法的POJO,如何保证变更可见性?
问题解答:主线程能否看到工作线程对WorkItem字段的修改?
核心结论
能,你的代码写法是线程安全的,主线程在synchronized(item)块内的读取操作可以保证看到工作线程对该WorkItem字段的所有修改。
具体原因分析
1. Synchronized的内存语义保证可见性
Java中,synchronized块有明确的内存语义:
- 当线程进入
synchronized块时,会将该锁保护的对象的所有字段从主内存刷新到当前线程的工作内存,确保读取到最新值; - 当线程退出
synchronized块时,会将当前线程工作内存中对该对象字段的修改写入主内存,同时释放锁; - 最重要的是同一个锁的解锁操作 happens-before 后续对该锁的加锁操作。
在你的代码里:
- 工作线程在
synchronized(item)块内修改item.field1,退出块时会把修改写入主内存并解锁; - 主线程在
finished.await()完成后,进入synchronized(item)块时会先获取锁,此时工作线程的解锁操作已经完成,根据happens-before规则,工作线程的修改对主线程完全可见。
2. CountDownLatch的额外保障
CountDownLatch的countDown()和await()方法也存在happens-before关系:所有调用countDown()的操作,都happens-beforeawait()方法返回。
在你的代码中,countDown()是在工作线程的synchronized块执行完成后才调用的,所以整个操作链条的happens-before关系是完整的:工作线程修改字段 → 退出synchronized块(解锁) → 调用countDown() → 主线程await()返回 → 进入synchronized块(加锁)读取字段
这进一步确保了修改的可见性。
对比你提到的“数组引用加锁”场景
你担心的类似“仅对数组引用加锁,而非单个元素”的问题,和当前场景完全不同:
- 那种场景下,锁的是数组对象本身,但修改的是数组元素,其他线程如果直接读取元素而不获取数组的锁,就无法保证可见性;
- 而你的代码中,每个
WorkItem自己作为锁对象,修改和读取操作都针对同一个WorkItem的锁,锁的范围和操作的对象完全匹配,不存在“锁错对象”的问题。
关于AtomicReferenceArray的作用
AtomicReferenceArray的设计目的是在不需要显式加锁的前提下,保证数组单个元素的可见性和原子操作。比如当你需要对数组元素进行无锁的原子更新,或者不想为了单个元素的可见性而加锁整个数组时,它就派上用场了。但你的代码已经通过synchronized块正确保证了可见性,所以不需要用到这类原子类。
内容的提问来源于stack exchange,提问作者Tobias Gierke
相关产品推荐
相关产品推荐

