【问题标题】:The visibility of variable which write after volatile variable write在 volatile 变量写入之后写入的变量的可见性
【发布时间】:2021-04-01 08:26:38
【问题描述】:
public class Test {
    private static volatile boolean flag = false;
    private static int i = 1;
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true;
            i += 1;
        }).start();
        new Thread(() -> {
            while (!flag) {
                if (i != 1) {
                    System.out.println(i);
                }
            }
            System.out.println(flag);
            System.out.println(i);
        }).start();
    }
}

变量i写在volatile变量标志之后,但代码输出true 2。看来第一个线程对i的修改对第二个线程可见。

根据我的理解,变量i应该写在flag之前,这样第二个线程才能知道变化。

【问题讨论】:

    标签: java multithreading parallel-processing volatile


    【解决方案1】:

    根据语言标准(§17.4):

    一个字段可能被声明为 volatile,在这种情况下,Java 内存模型 确保所有线程看到变量的一致值

    因此,非正式地,所有线程都可以查看该变量的必须更新值。

    然而,volatile子句不仅意味着确保目标变量的可见性保证,还意味着full volatile visibility guarantee,即:

    实际上,Java volatile 的可见性保证超出了 volatile 变量本身。可见性保证如下:

    如果线程 A 写入 volatile 变量,随后线程 B 读取相同的 volatile 变量,然后所有变量对 Thread 可见 A 在写入 volatile 变量之前,也会对 Thread 可见 B 读取 volatile 变量后。

    如果线程 A 读取 volatile 变量,然后所有变量都对线程 A 可见 读取 volatile 变量也会从主存中重新读取。

    根据我的理解,变量我应该写在前面 flag,那么第二个线程就可以知道变化了。

    “在写入 volatile 变量之前对线程 A 可见的所有变量”,它不是指对这些变量的操作

    【讨论】:

      【解决方案2】:

      您的代码遭受数据争用。

      数据竞争是指同一地址有 2 个内存操作未按先发生关系排序,并且这些操作中至少有一个是写入。

      在这种情况下,写入i 是问题所在。

      写入i 是在写入易失性变量flag 之后,因此在写入i 和读取i 之间没有任何关系。

      如果你在写flag之前写i,那么在关系之前会发生以下情况:

      1. 由于程序顺序规则,i 的写入发生在 flag 的写入之前
      2. 由于 volatile 变量规则,flag 的写入发生在 flag 的读取之前(在硬件级别,这是缓存一致性的任务)。
      3. 由于程序顺序规则,flag 的读取发生在 i 的读取之前。

      因为发生在关系之前是可传递的,所以i 的写入发生在i 的读取之前。

      所以就像您已经指出的那样,如果您将i 的写入移动到标志的写入之前;数据竞赛已经结束。

      【讨论】:

        【解决方案3】:

        内存模型定义了保证,但是任何事情都可能发生在它们之上。

        在 x86 上,所有写入都具有释放语义,一旦您写入变量,它的更新值将尽快从其他线程可见。

        因此,写入 volatile 变量之前的操作发生在读取它之后的操作之前,并不能阻止写入它之后的操作在读取它之后变得可见。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-03-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-11
          • 1970-01-01
          相关资源
          最近更新 更多