【发布时间】:2020-02-08 17:07:29
【问题描述】:
假设我有一个同时访问的字段,它被多次读取,很少写入。
public Object myRef = new Object();
假设一个线程 T1 将 myRef 设置为另一个值,每分钟一次,而其他 N 个线程将连续并发地读取 myRef 数十亿次。我只需要 myRef 最终对所有线程可见。
一个简单的解决方案是使用 AtomicReference 或像这样简单地使用 volatile:
public volatile Object myRef = new Object();
但是,afaik 易失性读取确实会产生性能成本。我知道这是微不足道的,这更像是我想知道的东西,而不是我真正需要的东西。所以我们不要关心性能,假设这是一个纯粹的理论问题。
所以问题归结为:有没有办法通过在写入站点做一些事情来安全地绕过对很少写入的引用的易失性读取?
经过一番阅读,看起来记忆障碍可能是我所需要的。因此,如果存在这样的构造,我的问题将得到解决:
- 写
- 调用屏障(同步)
- 一切都已同步,所有线程都将看到新值。 (在读取站点没有永久成本,它可能是陈旧的或在缓存同步时产生一次性成本,但在此之后它会全部返回到常规字段直到下一次写入)。
在 Java 中或一般情况下是否有这样的构造?在这一点上,我不禁想到,如果存在这样的东西,那么维护这些的更聪明的人已经将它合并到原子包中。 (不成比例的频繁读写可能不是一个需要关心的情况?)所以也许我的想法有问题,这样的构造根本不可能?
我已经看到一些代码示例出于类似目的使用“volatile”,利用它的发生前合同。有一个单独的同步字段,例如:
public Object myRef = new Object();
public volatile int sync = 0;
在写线程/站点时:
myRef = new Object();
sync += 1 //volatile write to emulate barrier
我不确定这是否有效,有些人认为这仅适用于 x86 架构。在阅读了 JMS 中的相关部分之后,我认为只有在 volatile 写入与来自需要查看 myRef 新值的线程的 volatile 读取相结合时,才能保证工作。 (所以不会摆脱 volatile 读取)。
回到我原来的问题;这可能吗?在Java中可能吗?是否可以在 Java 9 VarHandles 中的新 API 之一中使用?
【问题讨论】:
-
对我来说,听起来您已经进入了需要编写和运行一些模拟工作负载的实际基准测试的领域。
-
JMM 声明如果您的写入线程执行
sync += 1;并且您的读取线程读取了sync值,他们也会看到myRef更新。因为您只需要读者最终看到更新,因此您可以利用这一优势,仅在阅读器线程的每 1000 次迭代或类似情况下读取同步。但是您也可以使用volatile做类似的技巧 - 只需在读取器中缓存myRef字段进行 1000 次迭代,然后使用 volatile 再次读取它... -
@PetrJaneček 但是他不需要同步对线程之间共享的计数器变量的访问吗?不会是瓶颈吗?在我看来,这将更加昂贵。
-
@RavindraRanwala 每个读者都有自己的计数器,如果你的意思是数到 1000 次左右的迭代。如果您的意思是
sync字段,不,读者不会在每次迭代时触摸sync字段,当他们想检查是否有更新时,他们会机会主义地这样做。也就是说,一个更简单的解决方案是将myRef缓存 1000 轮,然后重新读取它... -
@PetrJaneček 谢谢,我认为这是一个可能的解决方案。但我想知道这是否可以使用通用、可靠的实现。
标签: java memory concurrency volatile