JavaMemoryModel:aJLSstatementaboutsequentialconsistencyseemsincorrect
I'm reading Chapter 17. Threads and Locks of JLS and the following statement about sequential consistency in Java seems incorrect to me:
If a program has no data races, then all executions of the program will appear to be sequentially consistent.
They define a data race as:
When a program contains two conflicting accesses (§17.4.1) that are not ordered by a happens-before relationship, it is said to contain a data race.
They define conflicted accesses as:
Two accesses to (reads of or writes to) the same variable are said to be conflicting if at least one of the accesses is a write.
And finally they have following about happens-before relationship:
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
My problem with the 1st statement is that I think I can come up with a Java program which has no data races and allows sequentially inconsistent executions:
// Shared code
volatile int vv = 0;
int v1 = 0;
int v2 = 0;
// Thread1 Thread2
v1 = 1;
v2 = 2;
vv = 10; while(vv == 0) {;}
int r1 = v1;
int r2 = v2;
System.out.println("v1=" + r1 + " v2=" + r2);
v1 = 3;
v2 = 4;
vv = 20;
In the code above I also showed with indentation how the threads' code is interleaved in runtime.
So, as I understand, this program:
- has no data races: reads of v1 and v2 in Thread2 are synchronized-with writes in Thread1
- can output
v1=1 v2=4(which violates sequential consistency).
As a result, the initial statement from JLS
If a program has no data races, then all executions of the program will appear to be sequentially consistent.
seems incorrect to me.
Am I missing something or did I make a mistake somewhere?
EDIT: user chrylis-cautiouslyoptimistic correctly pointed out that the code I gave can output v1=1 v2=4 with sequential consistency — the lines in threads' code simply should be interleaved a little bit differently.
So here is the slightly modified code (I've changed the order of reads) for which sequential consistency cannot output v1=1 v2=4, but everything still applies.
// Shared code
volatile int vv = 0;
int v1 = 0;
int v2 = 0;
// Thread1 Thread2
v1 = 1;
v2 = 2;
vv = 10; while(vv == 0) {;}
int r2 = v2;
int r1 = v1;
System.out.println("v1=" + r1 + " v2=" + r2);
v1 = 3;
v2 = 4;
vv = 20;
回答
你的错误是在子弹点#1:的读取v1和v2 不 同步,与。
还有之前发生创建关系,只能通过与互动vv,因此,例如在这种情况下,如果您添加vv到您的打印语句的开始,您将得到保证不会看vv=20,v2=4。由于您忙于等待vv变为非零但随后不再与它交互,因此唯一的保证是您将看到在变为非零之前发生的所有效果(1 和 2 的赋值)。您可能还会看到未来的效果,因为您没有任何进一步的发生前。
即使您将所有变量声明为 volatile,您仍然可以输出,v1=1,v2=4 因为变量的多线程访问没有定义的 order,并且全局序列可以是这样的:
- T1:写
v1=1 - T1:写
v2=2 - T1:写入
vv=10(线程 2不能在此之前退出 while 循环,并且保证看到所有这些效果。) - T2:阅读
vv=10 - T2:阅读
v1=1 - T1:写
v1=3 - T1:写
v2=4 - T2:阅读
v2=4
在这些步骤中的每一步之后,内存模型保证所有线程将看到 volatile 变量的相同值,但是您有数据竞争,这是因为访问不是原子的(分组的)。为了确保您在一个组中看到它们,您需要使用一些其他方法,例如在synchronized块中执行或将所有值放入一个记录类中并使用volatile或AtomicReference换出整个记录。
正式地,JLS 定义的数据竞争包括操作 T1(写 v1=3)和 T2(读 v1)(以及 v2 上的第二个数据竞争)。这些冲突的访问(因为T1访问是写),但在这两个事件发生后T2(读VV),他们没有下令在彼此相关。