如何理解一致性发生之前

2022-09-03 10:15:22

JLS的第17章中,它引入了一个概念:先发生,然后再一致。

如果对于 A 中的所有读取 r,其中 W(r) 是 r 看到的写入操作,则一组操作 A 在一致之前发生,则不是 hb(r, W(r)) 或 A 中存在写入 w 的情况,使得 w.v = r.v 和 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都不是易失性场。所以我的第四个问题是:我的理解是否正确?

在第二个执行顺序中:

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

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

2: B = 1;

4: A = 2;

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


答案 1

每个线程都可以位于具有不同内核上,具有自己的专用寄存器,Java 可以使用这些寄存器来保存变量值,除非您强制访问一致的共享内存。这意味着一个线程可以写入存储在寄存器中的值,并且此值在一段时间内对另一个线程不可见,例如循环或整个函数的持续时间。(毫秒并不少见)

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

在这两种情况下,使用 可确保读取和写入以一致的顺序进行,并且两个线程看到相同的值。这有时被描述为总是从主内存中读取,尽管情况并非如此,因为缓存可以直接相互通信。(因此,性能影响比您预期的要小得多)。volatile

在普通 CPU 上,缓存是“一致的”(不能保存陈旧/冲突的值)和透明的,而不是手动管理的。使数据在线程之间可见只是意味着在 asm 中执行实际加载或存储指令以访问内存(通过数据缓存),并选择性地等待存储缓冲区耗尽以提供排序 wrt。其他后期操作。


答案 2

发生之前

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

原子性 - 是操作的属性,可以作为单个事务完全执行,不能部分执行。例如 [示例]Atomic operations

可见性 - 如果一个线程进行了更改,则它们对于其他线程是可见的。 在 Java 5 之前volatilehappens-before

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

例如,这是一种有助于解决和问题的方法。发生之前的好例子是[关于],监视器[关于]happens-beforememory barrierVisibilityOrderingvolatilesynchronized

一个很好的例子是()实现()模式,它应该是原子的,并允许在多线程环境中更改变量。在以下情况下,您可以编写自己的实现:atomicityCompare and swapCAScheck then actCTACTA

  • volatile + synchronized
  • java.util.concurrent.atomic与(内存分配,实例化没有构造函数调用...)从中使用和CPU优势。sun.misc.UnsafeJava 5JNI

CASalgoritm 具有参数(A(地址)、O(旧值)、N(新值))。

If value by A(address) == O(old value) then put N(new value) into A(address), 
else O(old value) = value from A(address) and repeat this actions again

发生前

官方文档

可以按“发生前”关系对两个操作进行排序。如果一个操作发生在另一个操作之前,则第一个操作对第二个操作可见排序

enter image description here

以挥发性[关于]为例

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

让我们看一下这个例子:

// 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`(Thread A) was executed before

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

可见性 - 当更改/写入易失性变量时,它还会将所有以前的更改推送到RAM中 - 因此,所有非易失性变量的主内存都将是最新的,并且对另一个线程可见Thread A

订购

  • 写入线程 A 中的易失性变量之前的所有操作都将在之前调用。JVM能够对它们进行重新排序,但保证在写入线程A中的易失性变量之前,不会调用任何一个操作。

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

[并发性与并行性]


推荐