理解 happens-before 和同步
Understanding happens-before and synchronization
我正在尝试理解 Java happens-before order 概念,但有几件事看起来很混乱。据我所知,happens before 只是一组动作的顺序,并不提供任何关于实时执行顺序的保证。实际上(强调我的):
It should be noted that the presence of a happens-before relationship
between two actions does not necessarily imply that they have to take
place in that order in an implementation. If the reordering produces
results consistent with a legal execution, it is not illegal.
所以,它只是说,如果有两个动作 w
(写入)和 r
(读取)使得 hb(w, r),比 r
可能 实际上在执行中发生在 w
之前,但不能保证它会发生。写入 w
也被读取 r
.
观察到
如何确定两个动作在 运行 时间内相继执行?例如:
public volatile int v;
public int c;
操作:
Thread A
v = 3; //w
Thread B
c = v; //r
这里我们有 hb(w, r)
但这并不意味着 c
将在赋值后包含值 3
。我如何强制将 c
分配给 3? synchronization order是否提供此类保证?
我想将上述语句与一些示例代码流相关联。
为了理解这一点,让我们来看下面的 class,它有两个字段 counter
和 isActive
。
class StateHolder {
private int counter = 100;
private boolean isActive = false;
public synchronized void resetCounter() {
counter = 0;
isActive = true;
}
public synchronized void printStateWithLock() {
System.out.println("Counter : " + counter);
System.out.println("IsActive : " + isActive);
}
public void printStateWithNoLock() {
System.out.println("Counter : " + counter);
System.out.println("IsActive : " + isActive);
}
}
并假设有三个线程T1、T2、T3在StateHolder
的同一个对象上调用以下方法:
T1同时调用resetCounter()
,T2同时调用printStateWithLock()
,T1获得锁
T3 -> 在 T1 完成执行后调用 printStateWithNoLock()
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
并且第一行说,
根据上面的语句,它为 JVM、OS 或底层硬件提供了灵活性,可以对 resetCounter()
方法中的语句进行重新排序。当 T1 被执行时,它可以按以下顺序执行语句。
public synchronized void resetCounter() {
isActive = true;
counter = 0;
}
这与声明内联不一定意味着它们必须在实施中按该顺序发生。
现在从 T2 的角度来看,这种重新排序没有任何负面影响,因为 T1 和 T2 都在同一个对象上同步,并且 T2 保证看到两个字段的更改,无论重新排序是否发生,因为存在先行关系。所以输出总是:
Counter : 0
IsActive : true
这是根据声明,如果重新排序产生的结果与合法执行一致,则不违法
但是从 T3 的角度来看,通过这种重新排序,T3 可能会将 isActive
的更新值视为“truebut still see the
countervalue as
100”,尽管T1 已完成执行。
Counter : 100
IsActive : true
上面的下一点link进一步澄清了声明并说:
More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.
在此示例中,T3 遇到了此问题,因为它与 T1 或 T2 没有任何发生前关系。这符合 对于与它们不共享先发生关系的任何代码,不一定必须以该顺序出现。
注意: 为了简化案例,我们让单线程 T1 修改状态,T2 和 T3 读取状态。有可能
T1 更新 counter to 0
,稍后
T2 将 isActive 修改为 true
并在一段时间后看到 counter is 0
,
打印状态的 T3 仍然可以看到 only isActive as true but counter is 100
,尽管 T1 和 T2 都已完成执行。
关于最后一个问题:
we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3?
public volatile int v;
public int c;
Thread A
v = 3; //w
Thread B
c = v; //r
因为 v
是易变的,根据 Happens-before Order
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
因此可以安全地假设,当线程 B 尝试读取变量 v
时,它将始终读取更新后的值,并且 c
将在上面的代码中分配 3。
当 JLS 说线程 A 中的某个事件 X 与线程 B 中的事件 Y 建立了 先于 关系时,并不意味着 X 将先于 Y 发生。
表示IFX发生在Y之前,那么两个线程都会同意X发生在Y之前。也就是说,两个线程都将看到程序的内存处于与 X 发生在 Y 之前一致的状态。
全靠记忆。线程通过共享内存进行通信,但是当一个系统中有多个CPU时,都试图访问同一个内存系统,那么内存系统就会成为瓶颈。因此,典型的多 CPU 计算机中的 CPU 可以延迟、重新排序和缓存内存操作以加快速度。
这在线程不相互交互时效果很好,但当它们确实想要交互时会导致问题:如果线程 A 将值存储到普通变量中,Java 无法保证何时(甚至 if)线程 B 将看到值更改。
为了在重要的时候解决这个问题,Java 为您提供了同步 线程的某些方法。也就是说,让线程就程序内存的状态达成一致。 volatile
关键字和synchronized
关键字是线程间建立同步的两种方式。
我认为之所以叫它"happens before"是为了强调关系的传递性:如果你能证明A发生在B之前,并且你能证明B发生在C之前,那么根据JLS中指定的规则,你已经证明了A发生在C之前。
根据我的喜好解释@James 的回答:
// Definition: Some variables
private int first = 1;
private int second = 2;
private int third = 3;
private volatile boolean hasValue = false;
// Thread A
first = 5;
second = 6;
third = 7;
hasValue = true;
// Thread B
System.out.println("Flag is set to : " + hasValue);
System.out.println("First: " + first); // will print 5
System.out.println("Second: " + second); // will print 6
System.out.println("Third: " + third); // will print 7
if you want the state/value of the memory(memory and CPU cache) seen at the
time of a write statement of a variable by one thread,
线程 A 中 hasValue=true
(写入语句)看到的内存状态:
first
的值为 5,second
的值为 6,third
的值为 7
to be seen from every subsequent(why subsequent even though only one
read in Thread B in this example? We many have Thread C doing exactly
similar to Thread B) read statement of the same variable by another
thread,then mark that variable volatile
.
如果线程 A 中的 X (hasValue=true
) 发生在线程 B 中的 Y (sysout(hasValue)
) 之前,则行为应该如同 X 发生在之前同一线程中的 Y(在 X 处看到的内存值应该从 Y 开始相同)
Here we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3? Does synchronization order provide such guarantees?
还有你的例子
public volatile int v;
public int c;
Actions:
Thread A
v = 3; //w
Thread B
c = v; //r
您的示例中 v
不需要 volatile
。我们来看一个类似的例子
int v = 0;
int c = 0;
volatile boolean assigned = false;
操作:
线程 A
v = 3;
assigned = true;
线程 B
while(!assigned);
c = v;
assigned
字段易变。
- 只有在
assigned
变为 true
之后,我们才会在 Thread B
中有 c = v
语句(while(!assigned)
负责)。
- 如果我们有
volatile
— 我们有 happens before
.
happens before
意味着,如果我们看到 assigned == true
— 我们将看到语句 assigned = true
之前发生的所有事情:我们将看到 v = 3
.
- 所以当我们有
assigned == true
-> 我们有 v = 3
.
- 结果是
c = 3
。
没有volatile
会怎样
int v = 0;
int c = 0;
boolean assigned = false;
操作:
线程 A
v = 3;
assigned = true;
线程 B
while(!assigned);
c = v;
目前有 assigned
没有 volatile
。
Thread B
中c
的值在这种情况下可以等于0或3。所以没有任何保证
c == 3
.
我正在尝试理解 Java happens-before order 概念,但有几件事看起来很混乱。据我所知,happens before 只是一组动作的顺序,并不提供任何关于实时执行顺序的保证。实际上(强调我的):
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
所以,它只是说,如果有两个动作 w
(写入)和 r
(读取)使得 hb(w, r),比 r
可能 实际上在执行中发生在 w
之前,但不能保证它会发生。写入 w
也被读取 r
.
如何确定两个动作在 运行 时间内相继执行?例如:
public volatile int v;
public int c;
操作:
Thread A
v = 3; //w
Thread B
c = v; //r
这里我们有 hb(w, r)
但这并不意味着 c
将在赋值后包含值 3
。我如何强制将 c
分配给 3? synchronization order是否提供此类保证?
我想将上述语句与一些示例代码流相关联。
为了理解这一点,让我们来看下面的 class,它有两个字段 counter
和 isActive
。
class StateHolder {
private int counter = 100;
private boolean isActive = false;
public synchronized void resetCounter() {
counter = 0;
isActive = true;
}
public synchronized void printStateWithLock() {
System.out.println("Counter : " + counter);
System.out.println("IsActive : " + isActive);
}
public void printStateWithNoLock() {
System.out.println("Counter : " + counter);
System.out.println("IsActive : " + isActive);
}
}
并假设有三个线程T1、T2、T3在StateHolder
的同一个对象上调用以下方法:
T1同时调用resetCounter()
,T2同时调用printStateWithLock()
,T1获得锁
T3 -> 在 T1 完成执行后调用 printStateWithNoLock()
It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.
并且第一行说,
根据上面的语句,它为 JVM、OS 或底层硬件提供了灵活性,可以对 resetCounter()
方法中的语句进行重新排序。当 T1 被执行时,它可以按以下顺序执行语句。
public synchronized void resetCounter() {
isActive = true;
counter = 0;
}
这与声明内联不一定意味着它们必须在实施中按该顺序发生。
现在从 T2 的角度来看,这种重新排序没有任何负面影响,因为 T1 和 T2 都在同一个对象上同步,并且 T2 保证看到两个字段的更改,无论重新排序是否发生,因为存在先行关系。所以输出总是:
Counter : 0
IsActive : true
这是根据声明,如果重新排序产生的结果与合法执行一致,则不违法
但是从 T3 的角度来看,通过这种重新排序,T3 可能会将 isActive
的更新值视为“truebut still see the
countervalue as
100”,尽管T1 已完成执行。
Counter : 100
IsActive : true
上面的下一点link进一步澄清了声明并说:
More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.
在此示例中,T3 遇到了此问题,因为它与 T1 或 T2 没有任何发生前关系。这符合 对于与它们不共享先发生关系的任何代码,不一定必须以该顺序出现。
注意: 为了简化案例,我们让单线程 T1 修改状态,T2 和 T3 读取状态。有可能
T1 更新 counter to 0
,稍后
T2 将 isActive 修改为 true
并在一段时间后看到 counter is 0
,
打印状态的 T3 仍然可以看到 only isActive as true but counter is 100
,尽管 T1 和 T2 都已完成执行。
关于最后一个问题:
we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3?
public volatile int v;
public int c;
Thread A
v = 3; //w
Thread B
c = v; //r
因为 v
是易变的,根据 Happens-before Order
A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
因此可以安全地假设,当线程 B 尝试读取变量 v
时,它将始终读取更新后的值,并且 c
将在上面的代码中分配 3。
当 JLS 说线程 A 中的某个事件 X 与线程 B 中的事件 Y 建立了 先于 关系时,并不意味着 X 将先于 Y 发生。
表示IFX发生在Y之前,那么两个线程都会同意X发生在Y之前。也就是说,两个线程都将看到程序的内存处于与 X 发生在 Y 之前一致的状态。
全靠记忆。线程通过共享内存进行通信,但是当一个系统中有多个CPU时,都试图访问同一个内存系统,那么内存系统就会成为瓶颈。因此,典型的多 CPU 计算机中的 CPU 可以延迟、重新排序和缓存内存操作以加快速度。
这在线程不相互交互时效果很好,但当它们确实想要交互时会导致问题:如果线程 A 将值存储到普通变量中,Java 无法保证何时(甚至 if)线程 B 将看到值更改。
为了在重要的时候解决这个问题,Java 为您提供了同步 线程的某些方法。也就是说,让线程就程序内存的状态达成一致。 volatile
关键字和synchronized
关键字是线程间建立同步的两种方式。
我认为之所以叫它"happens before"是为了强调关系的传递性:如果你能证明A发生在B之前,并且你能证明B发生在C之前,那么根据JLS中指定的规则,你已经证明了A发生在C之前。
根据我的喜好解释@James 的回答:
// Definition: Some variables
private int first = 1;
private int second = 2;
private int third = 3;
private volatile boolean hasValue = false;
// Thread A
first = 5;
second = 6;
third = 7;
hasValue = true;
// Thread B
System.out.println("Flag is set to : " + hasValue);
System.out.println("First: " + first); // will print 5
System.out.println("Second: " + second); // will print 6
System.out.println("Third: " + third); // will print 7
if you want the state/value of the memory(memory and CPU cache) seen at the time of a write statement of a variable by one thread,
线程 A 中 hasValue=true
(写入语句)看到的内存状态:
first
的值为 5,second
的值为 6,third
的值为 7
to be seen from every subsequent(why subsequent even though only one read in Thread B in this example? We many have Thread C doing exactly similar to Thread B) read statement of the same variable by another thread,then mark that variable
volatile
.
如果线程 A 中的 X (hasValue=true
) 发生在线程 B 中的 Y (sysout(hasValue)
) 之前,则行为应该如同 X 发生在之前同一线程中的 Y(在 X 处看到的内存值应该从 Y 开始相同)
Here we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3? Does synchronization order provide such guarantees?
还有你的例子
public volatile int v;
public int c;
Actions:
Thread A
v = 3; //w
Thread B
c = v; //r
您的示例中 v
不需要 volatile
。我们来看一个类似的例子
int v = 0;
int c = 0;
volatile boolean assigned = false;
操作:
线程 A
v = 3;
assigned = true;
线程 B
while(!assigned);
c = v;
assigned
字段易变。- 只有在
assigned
变为true
之后,我们才会在Thread B
中有c = v
语句(while(!assigned)
负责)。 - 如果我们有
volatile
— 我们有happens before
. happens before
意味着,如果我们看到assigned == true
— 我们将看到语句assigned = true
之前发生的所有事情:我们将看到v = 3
.- 所以当我们有
assigned == true
-> 我们有v = 3
. - 结果是
c = 3
。
没有volatile
int v = 0;
int c = 0;
boolean assigned = false;
操作:
线程 A
v = 3;
assigned = true;
线程 B
while(!assigned);
c = v;
目前有 assigned
没有 volatile
。
Thread B
中c
的值在这种情况下可以等于0或3。所以没有任何保证
c == 3
.