如何理解发生 - 在一致之前

时间:2012-08-15 13:45:33

标签: java memory-model

chapter 17 of JLS中,它引入了一个概念:发生在一致之前。

  

一组动作A发生 - 在一致之前如果对于A中的所有读取r,其中W(r)是r看到的写入动作,则不是hb(r,W(r))的情况或者在A中存在写w,使得wv = rv和hb(W(r),w)和hb(w,r)“

根据我的理解,它等于以下词语: ......,既不是......也不是......

所以我的前两个问题是:

  • 是我的理解对吗?
  • “w.v = r.v”是什么意思?

它还给出了一个例子:17.4.5-1

Thread 1 Thread 2

B = 1; A = 2; 

r2 = A; r1 = B; 

在第一个执行顺序中:

1: B = 1;

3: A = 2;

2: r2 = A;  // sees initial write of 0

4: r1 = B;  // sees initial write of 0

订单本身已经告诉我们两个线程交替执行,所以我的第三个问题是:剩下的数字是什么意思?

在我的理解中,r2和r1的原因可以看出0的初始写入是A和B都不是volatile字段。所以我的第四个问题是:我的理解是否正确?

在第二个执行顺序中:

1: r2 = A;  // sees write of A = 2

3: r1 = B;  // sees write of B = 1

2: B = 1;

4: A = 2;

根据事先一致性的定义,不难理解这个执行顺序是否发生 - 在一致之前(如果我的第一个理解是正确的)。 所以我的第五个和第六个问题是:在现实世界中是否存在这种情况(读取后面会发生的写入)?如果确实如此,你能给我一个真实的例子吗?

6 个答案:

答案 0 :(得分:13)

每个线程可以在具有自己的缓存的不同核心上。这意味着一个线程可以写入存储在寄存器或其本地缓存中的值,并且该值在一段时间内对另一个线程不可见。 (毫秒并不少见)

一个更极端的例子是读取线程的代码被优化,假设因为它永远不会改变值,所以它不需要从内存中读取它。在这种情况下,优化代码永远不会看到另一个线程执行的更改。

在这两种情况下,使用volatile可确保读取和写入以一致的顺序发生,并且两个线程都看到相同的值。这有时被描述为始终从主存储器读取,但并非必须如此,因为缓存可以直接相互通信。 (因此,性能影响远小于您的预期)

答案 1 :(得分:3)

Java内存模型定义了程序中所有操作的部分排序,在之前称为发生。 为了保证线程Y能够看到操作X的副作用(如果X发生在不同的线程中,则无关紧要),发生在关系定义在XY之间 如果不存在这种关系,JVM可以重新排序程序的操作 现在,如果一个变量被许多线程共享和访问,并且由(至少)一个线程写入,如果关系之前没有对读取和写入进行排序,那么就会有数据竞争。
在正确的程序中没有数据竞赛 示例是在锁A上同步的2个帖子BX Thread A获取锁定(现在Thread B被阻止)并执行写入操作,然后释放锁定X。现在Thread B获取锁定X,并且由于Thread A的所有操作都已在锁定X之前完成,所以它们之前已经订购 Thread B主题X之后获取锁定A (并且对Thread B可见)的操作。
请注意,这发生在同一锁上同步上的操作上。在不同锁上同步的线程之间的关系之前发生 no

答案 2 :(得分:2)

实质上是正确的。要解决的主要问题是:除非你使用某种形式的同步,否则不能保证在你的程序顺序中写入之后的读取会看到该写入的效果,因为语句可能已被重新编码。 / p>

  

在现实世界中是否存在这种情况(读取后面会发生的写入)?如果确实如此,你能给我一个真实的例子吗?

从挂钟的角度来看,显然,读取无法看到尚未发生的写入效果。

从程序顺序的角度来看,因为如果没有正确的同步(在关系之前发生),语句可以重新排序,在程序中写入之前的读取可以在执行期间看到该写入的效果,因为它在JVM写入后执行。

答案 3 :(得分:0)

这意味着,如果没有适当的同步机制,您可能会直观地看到影响计数器。

volatile int A = 0;
volatile int B = 0;
1: B = 1;
2: r2 = A; // r2 guarantees the value 1
3: A = 2;         
4: r1 = B; // r1 guarantees the value 2

这是因为易变变量保证在关联之前发生。如果A和B都不易变,则系统可以对变量的评估重新排序,并且可能变得反直观。

答案 4 :(得分:0)

让我们看看并发性理论中的定义:

原子性-是一种操作属性,可以完全作为单个事务执行,不能部分执行。

可见性-如果一个线程进行了更改,则其他线程可以看到它们

排序-编译器可以更改源代码的操作/指令的顺序以进行一些优化。

发生之前

Official doc

  

可以通过事前关联来排序两个动作。如果一个动作发生在另一个动作之前,则第一个动作对第二个动作可见,并在第二个动作之前排序

enter image description here

volatile [About]为例

  

写入易变性字段发生在该字段的每个后续读取之前

让我们看一下示例:

// Definitions
int a = 1;
int b = 2;
volatile boolean myVolatile = false;

// Thread A. Program order
{
    a = 5;
    b = 6;
    myVolatile = true; // <-- write
}

//Thread B. Program order
{
    Thread.sleep(1000); //just to show that writing into `myVolatile` was executed before

    System.out.println(myVolatile); // <-- read
    System.out.println(a);  //prints 5, not 1
    System.out.println(b);  //prints 6, not 2
}

可见性-当Thread A 更改/写入一个 volatile 变量时,它还会将以前的所有更改推送到 RAM中-主内存,因此所有非易失性变量将保持最新状态,并在其他线程中可见

订购

  • 在写入Thread A 的volatile变量之前,所有操作都将被调用。 JVM能够对它们进行重新排序,但是保证在写入Thread A 中的volatile变量之前不会执行任何操作。

  • 在读取Thread B 中的volatile变量后,所有操作都将被调用。 JVM能够对它们进行重新排序,但是可以保证在读取Thread B 中的易失性变量之后,不会在任何操作之前对其进行调用。

{@ 1}监控器[About]

答案 5 :(得分:0)

Q1:我的理解对吗?

答:是的

Q2:“w.v = r.v”是什么意思?

A:w.v 的值与 r.v 的值相同

Q3:左数是什么意思?

A:我认为是“表 17.4-A. 语句重新排序导致的令人惊讶的结果 - 原始代码”中所示的语句 ID。但是你可以忽略它,因为它不适用于“另一个发生在一致性之前的执行顺序是:”的内容,所以左边的数字完全是狗屎。不要坚持。

Q4:在我的理解中,r2 和 r1 都可以看到初始写入 0 的原因是 A 和 B 都不是 volatile 字段。所以我的第四个问题是:我的理解是否正确?

A:这是一个原因。重新订购也可以。 “程序必须正确同步,以避免在重新排序代码时可以观察到的各种违反直觉的行为。”

Q5&6:在第二个执行顺序中......所以我的第五和第六个问题是:在现实世界中是否存在这种情况(读取看到稍后发生的写入)?如果是这样,你能给我一个真实的例子吗?

答:是的。代码中没有同步,每个线程读取都可以看到初始值的写入或其他线程的写入。

时间 1:线程 2:A=2

time 2: Thread 1: B=1 // 不同步,这里可以交错线程1的B=1

time 3: Thread 2: r1=B // r1 值为 1

时间 4: 线程 1: r2=A // r2 值为 2

注意“如果它的一组操作是先发生后一致的,则执行是先发生前一致的”