0

我在阅读 JVM 在读取 volatile 变量时提供的可见性保证时遇到了以下摘录: “当线程 A 写入 volatile 变量并且随后线程 B 读取相同的变量时,A 之前可见的所有变量的值在读取 volatile 变量后,写入 volatile 变量对 B 可见。”

我对 JVM 的这个保证有疑问。考虑以下一组类:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        POJO p = new POJO();
        new Th1(p).start();
        new Th2(p).start();
    }
}   
    public class Th1 extends Thread {
        private POJO p1 = null;
        public Th1(POJO obj) {
            p1 = obj;
        }
        @Override
        public void run() {
            p1.a = 10; // t = 1
            p1.b = 10; // t = 2
            p1.c = 10; // t = 5;
            System.out.println("p1.b val: " + p1.b); // t = 8
            System.out.println("Thread Th1 finished"); // t = 9
        }
    }
    
    public class Th2 extends Thread {
        private POJO p2 = null;
        public Th2(POJO obj) {
            p2 = obj;
        }
        @Override
        public void run() {
            p2.a = 30; // t = 3
            p2.b = 30; // t = 4
            int x = p2.c; // t = 6
            System.out.println("p2.b value: " + p2.b); // t = 7
        }
    }
    
    public class POJO {
        int a = 1;
        int b = 1;
        volatile int c = 1;
    }

想象一下 2 个线程 Th1 和 Th2 在不同的 CPU 中运行,它们的指令执行顺序由每行中的注释指示(在它们的运行方法中)。我的问题是:当代码“int x = p2.c;”时 在 t = 6 时执行,线程 Th2 可见的变量应根据上述段落从主存储器刷新。据我了解,此时主存储器将拥有来自 Th1 的所有写入。变量 p2.b 在 t = 7 打印时会显示什么值?

  • p2.b 的值是否会显示为 10,因为它的值是从 volatile 变量 p2.c 的读取中刷新的?
  • 或者它会以某种方式保留值 30?
4

2 回答 2

0

对于您的代码,不保证 p2.b 为 10 或 30。写入是竞争条件。

“当线程 A 写入 volatile 变量并随后线程 B 读取相同的变量时,在写入 volatile 变量之前对 A 可见的所有变量的值在读取 volatile 变量后对 B 可见。”

不保证在将 p1.c 写入 Th1 之后完成对 p2.c 的 Th2 读取。

对于您讨论的特定顺序,在 Th2 中读取 p2.c不会将 p2.b 的值恢复为 10。

于 2020-08-09T18:04:48.287 回答
-1

a在 的写入和读取之间没有发生在边缘之前a。由于它们是相互冲突的动作(其中至少有一个是写入)并且位于同一地址上,因此存在数据竞争,因此程序行为未定义。

我认为以下示例可以更好地解释您正在寻找的行为:

    public class Test {
        public static void main(String[] args) throws InterruptedException {
            POJO p = new POJO();
            new Th1(p).start();
            new Th2(p).start();
        }
    }   
    
    public class Th1 extends Thread {
        private POJO p1 = null;
        public Th1(POJO obj) {
            p1 = obj;
        }
        @Override
        public void run() {
            a=1;
            b=1;
        }
    }
    
    public class Th2 extends Thread {
        private POJO p2 = null;
        public Th2(POJO obj) {
            p2 = obj;
        }
        @Override
        public void run() {
            if(p.b==1)println("a must be 1, a="+p2.a);
        }
    }
    
    public class POJO {
        int a = 0;
        volatile int b = 0;
    }

在写入a和写入之间有一个发生在边缘之前b(程序顺序规则) 在写入b和后续读取之间有一个发生在边缘之前b(易失性变量规则) 在读取b和读取之间有一个发生在边缘之前a(程序顺序规则)的读取

a由于发生前的关系是传递的,因此在写入和读取之间存在发生前边缘a。所以第二个线程应该a=1从第一个线程中看到。

于 2020-08-10T05:01:07.370 回答