我在读第17章。jls的线程和锁以及以下关于java中顺序一致性的声明在我看来是不正确的:
如果一个程序没有数据竞争,那么程序的所有执行看起来都是顺序一致的。
他们将数据竞争定义为:
当一个程序包含两个冲突的访问时(§17.4.1)不按“发生在”关系排序的,称为包含数据竞争。
它们将冲突访问定义为:
如果对同一变量的两次访问(读或写)中至少有一次是写的,则称为冲突。
最后,他们有以下关于恋爱前发生的事情:
对易失性字段的写入(§8.3.1.4)在该字段的每次后续读取之前发生。
我对第1条语句的问题是,我想我可以想出一个java程序,它没有数据竞争,允许顺序不一致的执行:
// 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;
在上面的代码中,我还用缩进展示了线程的代码在运行时是如何交错的。
所以,据我所知,这个项目:
没有数据争用:thread2中v1和v2的读取与thread1中的写入同步
can输出 v1=1 v2=4
(这违反了顺序一致性)。
因此,jls最初的声明
如果一个程序没有数据竞争,那么程序的所有执行看起来都是顺序一致的。
对我来说似乎不正确。
我是遗漏了什么还是在什么地方犯了错?
编辑:用户chrylis谨慎的optimistic正确地指出我给出的代码可以输出 v1=1 v2=4
对于顺序一致性,线程代码中的行应该以稍微不同的方式进行交错。
所以这里是稍微修改的代码(我改变了读取顺序),顺序一致性无法输出 v1=1 v2=4
,但一切仍然适用。
// 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;
2条答案
按热度按时间kmbjn2e31#
你的错误在要点1:读取
v1
以及v2
不与同步。只有通过与客户的交互才能建立关系
vv
,例如在本例中vv
在打印声明的开头,您肯定不会看到vv=20,v2=4
. 既然你忙着等vv
变为非零,但之后不再与它交互,唯一的保证是您将看到它变为非零之前发生的所有效果(1和2的赋值)。你也可以看到未来的影响,因为你没有任何进一步的事情发生之前。即使您将所有变量声明为volatile,您仍然可以输出
v1=1,v2=4
因为变量的多线程访问没有定义的顺序,全局序列可以如下所示: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(write)组成 v1=3)和t2(读取 v1)(以及v2上的第二个数据竞赛)。这些是相互冲突的访问(因为t1访问是一个写访问),但是这两个事件都发生在t2(读)之后 vv),它们之间没有相互关联的顺序。
uxh89sit2#
事实上证明你错了比你想象的要容易得多。两个独立线程之间的操作在非常特殊的规则下是“同步的”,所有这些规则都在jsl的适当章节中定义。公认的答案是
synchronizes-with
不是一个实际的术语,但那是错误的(除非我没有理解意图或其中有错误)。因为你没有这样的特殊动作来建立同步订单(
SW
简而言之),介于Thread1
以及Thread2
,接下来的一切就像一座纸牌城堡,不再有意义。你提到
volatile
但同时要小心什么subsequent
意味着:对易失性字段的写入发生在该字段的每次后续读取之前。
它意味着一个读将观察到写。
如果你改变你的代码并建立一个
synchronizes-with
关系,从而隐含happens-before
像这样:您可以开始推理在if块中可能看到的内容。你可以从这里简单地开始:
如果x和y是同一线程的动作,并且x按程序顺序排在y之前,那么hb(x,y)。
好吧,那么
v1 = 1
happens-beforev2 = 2
以及happens-before
vv = 10
. 这样我们就建立了hb
在同一线程中的操作之间。我们可以通过“同步”不同的线程
synchronizes-with
通过适当的章节和适当的规则:对volatile变量v的写入与任何线程对v的所有后续读取同步
这样我们就建立了一个
SW
两个独立线程之间的顺序。这反过来又使我们能够建立一个HB
(以前发生过)现在,由于这一章和另一条规则:如果一个动作x与后面的动作y同步,那么我们还有hb(x,y)。
现在你有了一条链条:
所以直到现在,你才有证据证明,如果block
r1 = 1
以及r2 = 2
. 因为volatile
提供顺序一致性(无数据争用),每个线程都将读取vv
成为10
当然,威尔也会读v1
成为1
以及v2
成为2
.