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只保证线程将看到它的最新更新。这并不意味着它将在访问之前等待更新。但如果它正在更新中,它将完成,因此它的行为是原子化的。