【问题标题】:Is it possible to modify a non-volatile variable such that another thread is able to "see" the update?是否可以修改非易失性变量,以便另一个线程能够“看到”更新?
【发布时间】:2012-03-06 09:03:27
【问题描述】:

我有一个 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)上,对 volatile 变量的读取“几乎完全免费”但不是“完全免费”。

现在,由于我从 infinite 数量的线程中有 infinite 数量的读取,我希望该变量是 non-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();
    }
}

【问题讨论】:

  • 您可以使变量易变。这就是它的用途。此外,轮询通常不是一个糟糕的解决方案,也许还有比轮询更干净的替代方案。
  • @DavidHeffernan 我正在使用民意调查来证明这个问题..因为没有一个例子很难清楚地解释它。

标签: java synchronization


【解决方案1】:

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

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

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

  • volatile。对于您的情况,这可能是最简单、最轻便的选择。
  • AtomicBoolean。一些关于它与 volatile herehere 的讨论。
  • synchronized 块。在这种情况下矫枉过正。
  • java.util.concurrent.locks.Lock。比 synchronized 更多的功能。
  • Thread.join()。在这种情况下没有用(读取线程将等待写入器终止)。
  • Piggybacking. 想都别想。太多的事情可能会出错。

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

【讨论】:

  • 我正在尝试获得“完全免费”的读取,并且我准备进行非常昂贵的写入以换取“完全免费”的读取。是不是对于这个问题,没有比将其声明为 volatile 更好的解决方案了?
  • 好吧。有一种称为“piggypacking”的技术,它基于访问 any volatile 变量会将 all 缓存变量的本地副本与主内存同步的事实。因此,有时可能会在不同步的情况下写入一个变量,然后利用对不同变量的后续同步来确保主内存使用两个变量进行更新。 See this for reference。它非常脆弱,不应该用于任何事情,但仅供参考......
  • 我不太清楚“主内存”是什么意思。当您说“主内存”时,您是指所有线程使用的“内存”吗?
  • @Pacerier:这里的主内存是计算机的 RAM 内存。缓存是处理器缓存。这两个内存的存在是首先需要volatile的原因:如果所有处理器都直接访问RAM,那么就不会有内存/变量可见性问题(但会很慢)。见What every programmer should know about memory
  • 仅仅终止线程 B 对线程 A 没有影响。Tomasz 正在谈论 加入 线程。抱歉,您根本无法绕开 JVM JIT 编译器允许优化 reading variable 的事实,除非您使用一些同步构造(或 Thread.join()读取线程必须在即将终止的写入线程上调用join)。
【解决方案2】:

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

只有在以下情况下,一个线程对字段所做的更改保证对其他线程可见

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

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

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

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

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

【讨论】:

  • 关于第 4 点,你的意思是说如果“writer-thread”终止,“reader-thread”会看到 non-volatile 变量的更新,或者我是不是误会了什么?
  • @Pacerier:好吧,不是我,是 Doug Lea ;-)。该示例提到Thread.join(),我不认为这意味着“终止线程所做的每一个更改都立即被所有其他线程可见”。另外,您是否真的考虑启动新线程只是为了修改单个变量的选项?
  • 是的,如果它有效,因为对于我的用例,我很少需要编写,但是有大量线程的 无限 数量的读取。现在将变量声明为 volatile 会惩罚所有读取线程,我宁愿“严重惩罚”写入线程。
  • @Pacerier:说到volatile的读取性能,看看:stackoverflow.com/questions/4633866
  • 感谢链接 =D Btw 看看更新(解决方案-1),你认为它有效吗?
【解决方案3】:

我正在尝试获得“完全免费”的读取,并且我准备进行非常昂贵的写入以换取“完全免费”的读取。对于这个问题,真的没有比将其声明为 volatile 更好的解决方案了吗?

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

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


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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-04
    • 2018-09-02
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 2013-01-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多