假设我有一个Java ArrayList
,它显然不能是volatile
变量(volatile
,意思是:它的所有内部变量都是volatile
),并且我希望在第二个线程中看到它的最新状态,因为我可以确定在第一个线程结束后执行第二个线程(第一个可能修改了ArrayList
实例的线程)。
使用Memory Barrier这应该很容易。但是,如何构建影响ArrayList
实例的所有内部成员变量/状态的内存屏障?我知道同步是一个选项,但我不知道我应该在哪个对象上同步以达到预期的效果。
是否有关于此问题的官方参考,定义了最佳做法?
实现内存障碍的最简单方法似乎是我称之为fullFence()
。但似乎这不是推荐的方式; - )
答案 0 :(得分:1)
同步工作只要您在读取和写入时同步同一对象,就可以保证一致。我建议选择Java Concurrency In Practic深入了解并发。
在您的情况下,然后一个线程的结尾提供了一个内存屏障,因此读取可能已由第一个线程修改的任何值应该是安全的。请参阅https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html。
The final action in a thread T1 synchronizes-with any action in another thread T2 that detects that T1 has terminated.
T2 may accomplish this by calling T1.isAlive() or T1.join().
答案 1 :(得分:1)
我可以确定在第一个线程结束后执行第二个线程
让我们调用你的两个线程A和B,其中线程B是产生结果的线程,而线程A是想要使用结果的较长的线程。
您的A线程只需要拨打B.join()
。线程B在死亡之前写入内存的所有内容都保证在join()
调用返回后线程A可见。
另一种方法是用锁来保护有问题的数据。如果线程B在保持锁定的同时写入数据,那么线程A将保证在B释放锁定并且A获取它之后看到数据。
第三种方法是使用volatile
变量。您说有问题的数据无法声明volatile
,但访问volatile变量不仅会影响变量本身。如果线程B更新了数据然后更新了一些volatile int i;
那么在A读取i
之后,在更新i
之前B写入内存的所有内容都将在A中可见。
答案 2 :(得分:1)
你真正需要的是在列表之前发生的事情。状态更新结束时的全栅栏并不能保证读者在列表中看到一致的,完全更新的值,例如因为某些读取会卡在核心invalidation queue中,或者因为JIT会在寄存器中缓存一些值,因为VM不知道您正在处理共享数据,因此代码的行为可能与您期望的不同。如果你不害怕JIT(也许是因为你知道你正在以某种方式从内存中重新读取列表),那么只有x86体系结构才能完全覆盖,因为x86具有TSO(总存储顺序)属性,所以障碍(读取共享数据(LoadLoad和LoadStore)所需的“fences”是actually no-ops。但是围栏和障碍不是JMM或任何公共API的一部分,因此它们不适用于我们(不安全除外)。只要JMM只为well-formed execution提供正确性保证(使用事先发生和同步关系),你应该依赖这种机制,而不是棘手的不安全。
要在没有同步的情况下提供发生在之前的关系,您可以添加额外的volatile变量并在更新结束时写入并在从列表读取之前从中读取(在从此变量读取写入值之前写入volatile变量)。或者您可以使用相同的效果在列表实例上进行简单同步(在获取相同的监视器之前释放监视器)+互斥。