Java的volatile关键字存在不一致性问题。

huangapple go评论68阅读模式
英文:

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 &lt;= 1000) {
			if (&quot;T1&quot;.equals(name)) {
				if (count % 5 == 0) {
					resource.i = count;
					System.out.println(name + &quot;, Resource&#39;s value = &quot; + resource.i);
				}
			} else {
				System.out.println(name + &quot;, Resource&#39;s value = &quot; + resource.i);
			}
			count++;
		}
	}
}

public class TestVolatile {

	public static void main(String[] args) {
		Resource resource =  new Resource();
		new MyThread(resource, &quot;T1&quot;).start();
		new MyThread(resource, &quot;T2&quot;).start();
		new MyThread(resource, &quot;T3&quot;).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&#39;s value = 20
T2, Resource&#39;s value = 20
T2, Resource&#39;s value = 20
T2, Resource&#39;s value = 20
T2, Resource&#39;s value = 20
T1, Resource&#39;s value = 20
T1, Resource&#39;s value = 25
T1, Resource&#39;s value = 30
T1, Resource&#39;s value = 35
T3, Resource&#39;s value = 20
T3, Resource&#39;s value = 40
T3, Resource&#39;s value = 40
T3, Resource&#39;s value = 40
T3, Resource&#39;s value = 40
T1, Resource&#39;s value = 40
T3, Resource&#39;s value = 40
T1, Resource&#39;s value = 45
T1, Resource&#39;s value = 50
T1, Resource&#39;s value = 55
T1, Resource&#39;s value = 60
T3, Resource&#39;s value = 45
T3, Resource&#39;s value = 65
T3, Resource&#39;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 + &quot;, Resource&#39;s value = &quot; + 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 of name 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, &quot;T3, Resource&#39;s value = 20&quot;.

huangapple
  • 本文由 发表于 2020年8月30日 04:14:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/63651385.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定