英文:
Inconsistency using Java's volatile keyword
问题
我有以下的程序:
class Resource {
static volatile int i = 0;
}
class MyThread extends Thread {
Resource resource;
String name;
public MyThread(Resource resource, String name ) {
this.resource = resource;
this.name = name;
}
@Override
public void run() {
int count = 1;
while(count <= 1000) {
if ("T1".equals(name)) {
if (count % 5 == 0) {
resource.i = count;
System.out.println(name + ", 资源的值 = " + resource.i);
}
} else {
System.out.println(name + ", 资源的值 = " + resource.i);
}
count++;
}
}
}
public class TestVolatile {
public static void main(String[] args) {
Resource resource = new Resource();
new MyThread(resource, "T1").start();
new MyThread(resource, "T2").start();
new MyThread(resource, "T3").start();
}
}
根据某些条件,我通过一个名为 T1 的线程设置了 volatile 变量的值,期望这个值能够在另外两个线程 T2 和 T3 中反映出来。我进行了一个原子操作,但是我得到了意料之外的输出:
T2, 资源的值 = 20
T2, 资源的值 = 20
T2, 资源的值 = 20
T2, 资源的值 = 20
T2, 资源的值 = 20
T1, 资源的值 = 20
T1, 资源的值 = 25
T1, 资源的值 = 30
T1, 资源的值 = 35
T3, 资源的值 = 20
T3, 资源的值 = 40
T3, 资源的值 = 40
T3, 资源的值 = 40
T3, 资源的值 = 40
T1, 资源的值 = 40
T3, 资源的值 = 40
T1, 资源的值 = 45
T1, 资源的值 = 50
T1, 资源的值 = 55
T1, 资源的值 = 60
T3, 资源的值 = 45
T3, 资源的值 = 65
T3, 资源的值 = 65
.
.
我的理解是,一旦 T1 设置了 Resource 的 i 值,随后在 T2 和 T3 下打印 Resource 的 i 值应该是由 T1 设置的相同值。但在输出的第 9 行中,T1 = 35,但在第 10 行,T3 = 20。怎么会这样?我没有进行任何非原子操作。如果我漏掉了什么,请告诉我。谢谢。
英文:
I have the following program:
class Resource {
static volatile int i = 0;
}
class MyThread extends Thread {
Resource resource;
String name;
public MyThread(Resource resource, String name ) {
this.resource = resource;
this.name = name;
}
@Override
public void run() {
int count = 1;
while(count <= 1000) {
if ("T1".equals(name)) {
if (count % 5 == 0) {
resource.i = count;
System.out.println(name + ", Resource's value = " + resource.i);
}
} else {
System.out.println(name + ", Resource's value = " + resource.i);
}
count++;
}
}
}
public class TestVolatile {
public static void main(String[] args) {
Resource resource = new Resource();
new MyThread(resource, "T1").start();
new MyThread(resource, "T2").start();
new MyThread(resource, "T3").start();
}
}
Based on some condition, I am setting the value of volatile variable by one thread named T1, expecting the value to be reflected in other two threads T2 and T3. I am doing an atomic operation, but I am getting unexpected output:
T2, Resource's value = 20
T2, Resource's value = 20
T2, Resource's value = 20
T2, Resource's value = 20
T2, Resource's value = 20
T1, Resource's value = 20
T1, Resource's value = 25
T1, Resource's value = 30
T1, Resource's value = 35
T3, Resource's value = 20
T3, Resource's value = 40
T3, Resource's value = 40
T3, Resource's value = 40
T3, Resource's value = 40
T1, Resource's value = 40
T3, Resource's value = 40
T1, Resource's value = 45
T1, Resource's value = 50
T1, Resource's value = 55
T1, Resource's value = 60
T3, Resource's value = 45
T3, Resource's value = 65
T3, Resource's value = 65
.
.
My understanding is once T1 sets the values into Resource's i, following it, printing Resource's i value under T2 and T3 should have been the same value set by T1. In output line 9, T1 = 35 but in line 10, T3 = 20. How come ? I am not doing any non atomic operation.
Kindly let me know if I am missing out something. Thanks.
答案1
得分: 2
即使代码看起来像是“一行代码”,背后实际上并非如此。
T1在 if (count%5 == 0)
内部执行以下操作:
写入 resource.i
拼接 `name` 和静态文本
读取 resource.id
将前面的文本与 resource.i 的值拼接
调用 System.out.println
T2 和 T3 执行以下操作:
拼接 `name` 和静态文本
读取 resource.id
将前面的文本与 resource.i 的值拼接
调用 System.out.println
上述所有操作都会同时执行,因此可能的一个时间线如下:
T2 拼接 `name` 和静态文本
T2 读取 resource.id
T1 写入 resource.i
T1 拼接 `name` 和静态文本
T1 读取 resource.id
T1 将前面的文本与 resource.i 的值拼接
T1 调用 System.out.println
T2 将前面的文本与 resource.i 的值拼接
T2 调用 System.out.println
英文:
Even if code looks like "one-liner", behind the scene it is not
T1 does within if (count%5 == 0)
write resource.i
concatenate `name` and static text
read resource.id
concatenate previous text and value of resource.i
call System.out.println
T2 and T3 do
concatenate `name` and static text
read resource.id
concatenate previous text and value of resource.i
call System.out.println
All operations above are executed concurrently, so one of possible timelines could be
T2 concatenate `name` and static text
T2 read resource.id
T1 write resource.i
T1 concatenate `name` and static text
T1 read resource.id
T1 concatenate previous text and value of resource.i
T1 call System.out.println
T2 concatenate previous text and value of resource.i
T2 call System.out.println
答案2
得分: 1
IMO,你的问题在这里:
System.out.println(name + ", 资源的值 = " + resource.i);
这并不是一个原子操作。它至少必须:
- 获取
resource.i
的值, - 将该值转换为十进制表示,
- 通过复制
name
、给定的文字字符串和之前计算得到的十进制数的值来构造一个新的String
, - 最后,它必须在新字符串上调用
System.out.println
。
它可能会执行更多操作(例如,它可能会构造不止一个新对象)。
在调用线程获取 resource.i
值的时刻和最终在 System.out.println
调用内的某个 synchronized
语句中进入的时刻之间可能会发生很多事情。例如,很有可能在 T3
线程获取 resource.i
值(值为20)的时刻和 T3
线程最终使用字符串 "T3, 资源的值 = 20"
调用 println
的时刻之间,T1
线程可能会调用 println
三次。
英文:
IMO, Your problem is here:
System.out.println(name + ", Resource's value = " + resource.i);
That is not an atomic operation. It must at least;
- Fetch the value of
resource.i
, - Convert the value to decimal notation,
- Construct a new
String
by copying the values ofname
and the given literal string and the decimal number previously computed, - And then finally, it must call
System.out.println
on the new string.
It might do more than that (e.g., it might construct more than just one new object).
A lot can happen in between the moment when a calling thread fetches the value of resource.i
and the moment when it finally enters a synchronized
statement somewhere within the System.out.println
call. For example, It's entirely believable that the T1
thread could call println
three times in between the moment when the T3
thread fetches resource.i
(value equals 20), and the moment when the T3
thread finally calls println
with the string, "T3, Resource's value = 20"
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论