是否可以修改非易失性变量,以便另一个线程能够“看到”更新?

我有一个Thread-X,它每秒读取一个非易失性变量,没有任何同步手段。

现在我想知道是否有某种方法可以修改Thread-Y上的非易失性变量,以便Thread-Y的写入(最终)在Thread-X上可见?

public class Test { private static boolean double = 1; // this variable is // strictly not volatile public static void main(String args[]) { new java.lang.Thread(new java.lang.Runnable() { @Override public void run() { while (true) { System.out.println(variable); try { java.lang.Thread.currentThread().sleep(1000); } catch (java.lang.InterruptedException e) { } } } }).start(); // line 17 new java.lang.Thread(new java.lang.Runnable() { @Override public void run() { // the task is to change variable to "2" such the write // is (eventually) registered by the other threads // allowed to use any synchronization/locking techniques // required, but line 1 to line 17 must not be changed } }).start(); } } 

是否可以修改非易失性变量,使得在没有任何同步技术( 原始读取 )的情况下读取它的另一个线程最终能够“看到”更新?

背景:

我需要从大量线程中读取一个变量, 无数次。

从我的理解(纠正我,如果我错了),在大多数cpus(例如x86)上读取volatile变量是“几乎完全免费”但不是“完全免费”。

既然我从无数个线程中读取了大量的数据,我希望变量是非易失性的 。 然而,一旦在蓝色的月亮中,变量需要更新。 在我的用例中,更新该变量需要花费多少并不重要,但该更新最终必须由读取器线程读取。

解决方案:

根据Tomasz的评论 ,我已经构建了这个解决方案,我想知道解决方案-1是否有缺陷或者它是否可靠?

 public class Solution1 { private static double variable = 1; // this variable is // strictly not volatile public static void main(String args[]) { new java.lang.Thread(new java.lang.Runnable() { @Override public void run() { while (true) { System.out.println(variable); try { java.lang.Thread.currentThread().sleep(1000); } catch (java.lang.InterruptedException e) { } } } }).start(); // line 17 new java.lang.Thread(new java.lang.Runnable() { @Override public void run() { variable = 2; // writer-thread now terminates, // is it guaranteed that when it // "terminates successfully", variable // is updated on the reader-thread ? } }).start(); } } 

基于Joonas的评论 ,我已经构建了这个解决方案,我想知道解决方案-2是否有缺陷或者它是否可靠?

 public class Solution2 { private static double variable = 1; // this variable is // strictly not volatile private static volatile boolean lock = false; public static void main(String args[]) { new java.lang.Thread(new java.lang.Runnable() { @Override public void run() { while (true) { System.out.println(variable); try { java.lang.Thread.currentThread().sleep(1000); } catch (java.lang.InterruptedException e) { } } } }).start(); // line 17 new java.lang.Thread(new java.lang.Runnable() { @Override public void run() { variable = 2; lock = false; // does this line guarantee // that other threads will now // see the update to variable (piggypacking)? // now let's assume this thread doesn't terminate } }).start(); } } 

是否可以修改非易失性变量,使得在没有任何同步技术(原始读取)的情况下读取它的另一个线程最终能够“看到”更新?

不可以。必须使用某些同步技术,否则允许(JIT)编译器优化你的行到System.out.println(false); (如果false是该线程首次看到的值)。 也就是说,它可以优化读取变量。

我不知道它实际上有多大可能做到这一点,但根据Java内存模型它是可以的,所以你的选择是:

  • volatile 。 对于您的情况,这可能是最简单和最轻的选择。
  • AtomicBoolean 。 在这里和这里讨论它与易变性。
  • synchronized块。 在这种情况下矫枉过正。
  • java.util.concurrent.locks.Lock 。 比synchronized更多的function。
  • Thread.join() 。 在这种情况下没用(读取线程会等待编写器终止)。
  • 捎带。 甚至不要考虑它。 太多可能会出错的事情。

只需使用volatile ,让JVM担心有效地实现它。 它并不昂贵。

引用Doug Lea的Java并发编程 同步和Java内存模型 :

只有在以下条件下,才能保证一个线程对字段的更改对其他线程可见

  • 写入线程释放同步锁 ,读取线程随后获取相同的同步锁。

  • 如果一个字段被声明为volatile ,则在写入器线程执行任何进一步的内存操作之前,写入其中的任何值都会被写入器线程刷新并使其可见(即,出于手头的目的,它立即被刷新)。 读取器线程必须在每次访问时重新加载volatile字段的值。

  • 线程第一次访问对象的字段时,它会看到字段的初始值或自某个其他线程写入的值。

  • 当一个线程终止时 ,所有写入的变量都被刷新到主存储器。 例如,如果一个线程使用Thread.join在另一个线程的终止上同步,那么可以保证看到该线程产生的影响(参见§4.3.2)。

最后两个选项不适用于您的情况,因此您需要volatilesynchronized ,抱歉。 请注意, AtomicInteger.get()只返回volatile值,因此除了额外的图层之外什么也得不到。

我正在努力获得“完全免费”的读取,并且我准备做一个非常昂贵的写作来换取“完全免费”的读取。 对于这个问题,没有比将其声明为易失性更好的解决方案吗?

没有什么是完全免费的。 读取未被改变的易失性变量可以是亚纳秒,这可能足够快。 如果您在写完后阅读它可能需要5纳秒。 无论你的“免费”读取,分支可能需要5-10纳秒,如果你做的事情就像花费时间一样简单,例如使用System.nanoTime(),这可能需要20-180纳秒,具体取决于你的操作系统。 您的阅读成本应该在您关注的列表中非常低。

例如,您应该担心代码是否已预热,以便编译而不是解释。 这可以产生更大的差异(见下文)


在许多情况下可能需要使用volatile ,但我不相信你有其中之一。

最常见的情况是@Joonas Pulakka提到JIT以你不想要的方式优化字段,即它停止读取值并使其成为局部变量,因为它不易变。

在循环快速连续迭代10,000次之后,可以进行此优化。 在你的情况下,它不是快速连续,但超过2.7小时,所以JIT可能永远不会优化代码。