0

假设我有一个 Java ArrayList,它显然不能是一个volatile变量(volatile在某种意义上:它的所有内部变量都是volatile),并且想在第二个线程中查看它的最新状态,因为我可以确定第二个线程是在第一个线程结束(可能已修改ArrayList实例的第一个线程)。

使用内存屏障应该很容易。但是我怎样才能构造这样一个影响ArrayList实例的所有内部成员变量/状态的内存屏障呢?我知道同步是一种选择,但我不知道我应该在哪个对象上同步才能达到预期的效果。

有没有关于这个问题的官方参考,定义一个最佳实践?

在我看来,实现内存屏障最直接的方法是调用类似fullFence(). 但似乎这不是推荐的方式;-)

4

3 回答 3

1

你真正需要的是发生在列表之前。状态更新结束时的全栅栏并不能保证读取器会在列表中看到一致的、完全更新的值,例如,因为某些读取会卡在核心失效队列中,或者因为 JIT 会将一些值缓存在寄存器中,因为 VM 不会知道您正在处理共享数据,因此代码的行为可能与您期望的方式不同。如果你不害怕 JIT(也许是因为你知道你正在以某种方式从内存中重新读取列表),那么只有在 x86 架构上,full-fence 就足够了,因为 x86 具有 TSO(总存储顺序)属性,所以障碍 ( ~fences) 需要读取共享数据(LoadLoad 和 LoadStore)实际上是无操作的. 但是栅栏和障碍不是 JMM 或任何公共 API 的一部分,因此我们无法使用它们(Unsafe 除外)。只要 JMM 只为格式良好的执行提供正确性保证(使用之前发生和同步的关系),您就应该依赖这种机制,而不是棘手的 Unsafe。

要提供不同步的发生之前的关系,您可以添加额外的 volatile 变量并在更新结束时写入它,并在从列表读取之前读取它(写入 volatile 变量发生在从该变量读取写入值之前)。或者您可以在列表实例上进行简单的同步,具有相同的效果(释放监视器发生在获取相同的监视器之前)+ 互斥。

于 2016-05-05T17:30:49.513 回答
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().
于 2016-05-05T17:18:16.363 回答
1

我可以确定第二个线程在第一个线程结束后执行

让我们称您的两个线程 A 和 B,其中线程 B 是产生结果的线程,线程 A 是想要使用结果的运行时间较长的线程。

您的 A 线程只需要调用B.join(). 线程 B 在死前写入内存的所有内容都保证在join()调用返回后对线程 A 可见。


另一种方法是用锁保护有问题的数据。如果线程 B 在持有锁的同时写入数据,那么线程 A 将保证在 B 释放锁并且 A 获得它之后看到数据。


第三种方法是使用volatile变量。你说有问题的数据不能被声明volatile,但是访问一个 volatile 变量影响的不仅仅是变量本身。如果线程 B 更新数据,然后更新一些,volatile int i;那么 B 在更新之前写入内存的所有内容i在 A 读取之后对 A 可见i

于 2016-05-05T17:24:56.800 回答