有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

我有一个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();
    }
}

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

背景:

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

据我所知(如果我错了,请纠正我),在大多数CPU(例如x86)上,易变变量的读取“几乎完全免费”,但不是“完全免费”

现在,由于我有一个无限线程数的无限读取数,我希望变量是非易失性的。然而,每隔一段时间就需要更新变量。在我的用例中,更新该变量的代价实际上并不重要,但该更新最终必须能够被读线程读取

解决方案:

基于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) 个答案

  1. # 1 楼答案

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

    Changes to fields made by one thread are guaranteed to be visible to other threads only under the following conditions:

    • A writing thread releases a synchronization lock and a reading thread subsequently acquires that same synchronization lock.

    • If a field is declared as volatile, any value written to it is flushed and made visible by the writer thread before the writer thread performs any further memory operation (i.e., for the purposes at hand it is flushed immediately). Reader threads must reload the values of volatile fields upon each access.

    • The first time a thread accesses a field of an object, it sees either the initial value of the field or a value since written by some other thread.

    • As a thread terminates, all written variables are flushed to main memory. For example, if one thread synchronizes on the termination of another thread using Thread.join, then it is guaranteed to see the effects made by that thread (see §4.3.2).

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

  2. # 2 楼答案

    Is it possible to modify a non-volatile variable such that another thread which reads it without any synchronization techniques (raw read) is able to "see" the update eventually?

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

    我不知道这样做的可能性有多大,但根据Java内存模型,这没关系,所以您可以选择:

    • volatile。这可能是最简单、最轻的选择
    • AtomicBoolean。关于它与volatile herehere的一些讨论
    • synchronized块。在这种情况下,杀伤力太大了
    • java.util.concurrent.locks.Lock。比^{中的功能更多
    • Thread.join()。在这种情况下不有用(读取线程将等待写入程序终止)
    • Piggybacking.想都别想。太多可能会出错的事情

    只需使用volatile,让JVM担心如何高效地实现它It's not expensive.

  3. # 3 楼答案

    I'm trying to get "totally free" reads, and I'm prepared to do a very expensive write in exchange for "totally free" reads. Is it true that for this problem, there is no better solution than declaring it as volatile?

    没有什么是完全免费的。对未被改变的易失性变量的读取可以是亚纳秒,这可能已经足够快了。如果你在一次书写后阅读,可能需要5纳秒。不管你的“免费”阅读时间是多少,如果你做了一些简单的事情,比如花时间在系统上,分支可能需要5-10纳秒。nanoTime(),这可能需要20-180纳秒,具体取决于您的操作系统。在你关注的问题列表中,阅读的成本应该非常低

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


    volatile在很多情况下都是需要的,但我相信你没有

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

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