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

时间:2012-03-06 09:03:27

标签: java synchronization

我有一个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's comment,我已经构建了这个解决方案,我想知道解决方案-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's comment,我已经构建了这个解决方案,我想知道解决方案-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();
    }
}

3 个答案:

答案 0 :(得分:5)

  

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

否。必须使用某种同步技术,否则允许(JIT)编译器将您的行优化为System.out.println(false);(如果false是第一次看到的值通过那个线程)。也就是说,它可以优化读取变量。

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

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

只需使用volatile并让JVM担心有效地实施它。 It's not expensive.

答案 1 :(得分:3)

Doug Lea从Synchronization and the Java Memory Model引用Concurrent Programming in Java

  

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

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

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

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

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

  •   

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

答案 2 :(得分:3)

  

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

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

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


在许多情况下可能需要

volatile,但我不相信你有其中一种。

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

这种优化可以在循环快速连续迭代10,000次后发生。在您的情况下,它不是快速连续,但超过2.7小时,因此JIT可能永远不会优化代码。