Java中的易失性变量是否应该在第二个线程上不可见?

Java中的易失性变量是否应该在第二个线程上不可见?,java,java-memory-model,Java,Java Memory Model,我有一个小示例应用程序,它有两个线程a和B。在每次设置一个易失性变量的值之前,a为x设置值,B为y设置值,B也会打印出这两个变量。当此游戏重复多次时,有时x的值在B中可见,但有时不可见(即使x和y都不稳定)。为什么呢 public class Vol2 { public static volatile int x = 0; public static volatile int y = 0; public static void main(String[] args)

我有一个小示例应用程序,它有两个线程
a
B
。在每次设置一个易失性变量的值之前,
a
x
设置值,
B
y
设置值,
B
也会打印出这两个变量。当此游戏重复多次时,有时
x
的值在
B
中可见,但有时不可见(即使
x
y
都不稳定)。为什么呢

public class Vol2 {

    public static volatile int x = 0;
    public static volatile int y = 0;

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 50; i++) {

            x = 0;
            y = 0;


            Thread t1 = new Thread(() -> {
                doWork();
                x = 5;
            });

            Thread t2 = new Thread(() -> {
                doWork();
                y = 6;
                System.out.println("x: " + x + ", y: " + y);
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

        }
    }

    public static void doWork() {
        int s = 0;
        for (int i = 0; i < 1_000_000; i++) {
            s += i;
        }
    }
}
公共类Vol2{
公共静态volatile int x=0;
公共静态易失性int y=0;
公共静态void main(字符串[]args)引发InterruptedException{
对于(int i=0;i<50;i++){
x=0;
y=0;
线程t1=新线程(()->{
销钉();
x=5;
});
线程t2=新线程(()->{
销钉();
y=6;
System.out.println(“x:+x+”,y:+y);
});
t1.start();
t2.start();
t1.join();
t2.连接();
}
}
公共静态无效工作(){
int s=0;
对于(int i=0;i<1_000;i++){
s+=i;
}
}
}

线程
t1
t2
可以同时执行。不能保证
t1
t2
分配
y
并读取
x

之前会分配
x
。分配变量,然后做一些工作。这使每个线程都有机会设置值。线程总是需要有限的时间来启动和异步启动,因此无法预测开始时的事件

public class Vol2 {

    public static volatile int x = 0;
    public static volatile int y = 0;

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 50; i++) {

            x = 0;
            y = 0;


            Thread t1 = new Thread(() -> {
                x = 5;
                doWork();
            });

            Thread t2 = new Thread(() -> {
                y = 6;
                doWork();
                System.out.println("x: " + x + ", y: " + y);
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

        }
    }

    public static void doWork() {
        int s = 0;
       try {
           Thread.sleep(10);
       } catch (InterruptedException ie){}
    }
}
公共类Vol2{
公共静态volatile int x=0;
公共静态易失性int y=0;
公共静态void main(字符串[]args)引发InterruptedException{
对于(int i=0;i<50;i++){
x=0;
y=0;
线程t1=新线程(()->{
x=5;
销钉();
});
线程t2=新线程(()->{
y=6;
销钉();
System.out.println(“x:+x+”,y:+y);
});
t1.start();
t2.start();
t1.join();
t2.连接();
}
}
公共静态无效工作(){
int s=0;
试一试{
睡眠(10);
}捕获(中断异常)
}
}

您的示例存在一些微妙的问题。首先是,
doWork
可能完全没有用处,它没有任何副作用,
JIT
可以完全消除这一点。想一想:既然这个循环纯粹是一个没有人看到的局部事物,那么为什么首先要这样做呢

然后,您对
Thread::start
有一个错误的理解,这已经向您解释了<代码>t1.start()
调度要启动的线程,这并不意味着它将在
t2.start()之前完成(甚至启动)

那么,至少在我看来,你在工作中使用了错误的工具。你需要一个由专家为这类事情量身定做的工具。下面是使用它时代码的外观:

@JCStressTest
@State
@Outcome(id = "0, 0", expect = Expect.FORBIDDEN, desc = "can not happen")
@Outcome(id = "0, 6", expect = Expect.ACCEPTABLE, desc = "writerY only")
@Outcome(id = "5, 6", expect = Expect.ACCEPTABLE, desc = "both")
public class TwoThreads {


    volatile int x = 0;
    volatile int y = 0;

    @Actor
    void writerX(II_Result r) {
        x = 5;
    }

    @Actor
    void writerY(II_Result r) {
        y = 6;
        r.r1 = x;
        r.r2 = y;
    }

}
如何运行它以及它意味着什么-对你来说是一个练习,但主要的一点是运行它只会给你带来两种可能的结果:

5, 6 --> meaning writerX finished its work before writerY
或者翻译成您的代码,
t1
t2
之前

0, 6 --> meaning writerY finished its work before writerX

因此,由
writerX
编写的
x=5
丢失且从未记录。或者,转换为您的代码,
t2
t1
之前完成

经过调整和几次尝试(3),我仍然得到x=0。根据我在答案中的评论,这是可以预料的,并进一步证明了线程的不可预测性。然后这个过程需要更长的睡眠时间。更改为200毫秒,因为10毫秒的等待时间并不总是足够的。操作系统加载程序、JVM启动都会导致这种行为。我刚刚用10毫秒和100毫秒进行了测试,每次迭代后我都会得到预期值,x=5,y=6。这是否应该完全独立于线程所需的时间,因为变量声明为volatile,所以在任何情况下都是从RAM中提取的?在这种情况下,Volatile并不重要。Volatile只保证线程将看到它的最新更新。这并不意味着它将在访问之前等待更新。但如果它正在更新中,它将完成,因此它的行为是原子化的。