变量应该在 2 运行 个线程之间可变吗?
Should a variable be volatile between 2 running threads?
在这种情况下 int a
是否应该是易变的以保证线程之间的可见性?
private volatile static int a = 0;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
a = 10;
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(a);
}
});
t1.start();
t2.start();
}
输出
10
happens-before is clearly defined in the language specification,从阅读开始;开始。
然后要完全了解正在发生的事情,您需要知道 Program order is, as well as synchronization order.
简单来说,请看下面:
private volatile static int a = 0;
private static int b = 0;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
b = 100;
a = 10;
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
if(a == 10){
System.out.println(b);
}
}
});
t1.start();
t2.start();
}
您唯一的保证是,当且仅当 t2
打印某些内容时,它始终是 100
。这是因为 t2
已经 看过 一个 volatile write
到 a
。发生这种情况是因为已经建立了一个“先于发生”,从写入线程到读取线程,并且在 a = 10
之前完成的每个动作都保证对已经看到 a
的线程可见10
.
Could you explain yourself a bit further on "happens-before"?
关于“之前发生”最重要的是要记住它是 transitive relation。这意味着,如果 Java 语言规范 (JLS) 承诺 A“发生在”B 之前,并且它承诺 B“发生在”C 之前,那么您可以推断出 A“发生在”C 之前的承诺。
JLS 表示写入某些 volatile
变量“发生在”随后读取同一变量之前。
好吧!听起来很明显,不是吗?
但这不显而易见,因为 JLS 不 为非易失性变量提供相同的保证。如果处理器 A 将值 7 写入非易失性 int,然后一段时间后处理器 B 写入 5,则 JLS 不保证一段时间后变量的最终值为 5。处理器 B 将看到 5 (这是一个不同的“发生在之前”的承诺,见下文)。处理器 A 可以看到 5 或 7,任何其他处理器可以看到 5 或 7 或变量最初具有的任何值(例如 0)。
volatile
承诺如何提供帮助
假设我们有
volatile boolean flag = false;
/*non-volatile*/ int i = 0;
假设线程 A 这样做:
i = 7;
flag = true;
假设线程 B 这样做:
if (flag) {
System.out.println(i);
}
else {
System.out.println("Bleah!");
}
线程 B 可以打印“7”,也可以打印“Bleah!”但由于“先于发生”保证,我们绝对知道线程 B 永远不会打印“0”。为什么不呢?
线程 A 在设置 flag = true
之前设置 i = 7
。 JLS 保证如果单个线程在执行第二条语句之前执行一条语句,那么第一条语句“发生在”第二条语句之前。 (这听起来 惊人地 显而易见,但同样,它不应该。很多与线程有关的事情 并不 显而易见。)
线程 B 在打印 i
之前测试 flag
。所以*IF*线程A之前设置了flag=true
那么我们知道i
一定等于7
:传递性:i=7
”发生在“ flag=true
,以及对 volatile flag
的写入, 如果它发生了 ,“发生在”读取相同的 flag
。
如果真的发生了
数据竞争和竞争条件
要记住的最重要的事情是,当 JLS 承诺 A“发生在”B 之前时,他们并不是说 A 总是确实发生在 B 之前:他们是说你可以依赖于这种传递关系。他们说 如果 A 确实发生在 B 之前,那么所有“发生在”A 之前的事情也一定确实发生在 B 之前。
程序可以打印“Bleah!”因为没有什么能阻止线程 B 在线程 A 设置它之前测试 flag
。有人称之为“数据竞赛”。两个线程正在“竞赛”,看哪个线程先到达 flag
,程序的结果取决于哪个线程赢得比赛。
当程序的正确性取决于数据竞争的结果时,我们中的一些人称之为“竞争条件”,这确实令人头疼。无法保证具有竞争条件的程序在您的测试期间不会做一千次正确的事情,然后在对您的客户最重要的时候做错误的事情。
在这种情况下 int a
是否应该是易变的以保证线程之间的可见性?
private volatile static int a = 0;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
a = 10;
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(a);
}
});
t1.start();
t2.start();
}
输出
10
happens-before is clearly defined in the language specification,从阅读开始;开始。
然后要完全了解正在发生的事情,您需要知道 Program order is, as well as synchronization order.
简单来说,请看下面:
private volatile static int a = 0;
private static int b = 0;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
b = 100;
a = 10;
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
if(a == 10){
System.out.println(b);
}
}
});
t1.start();
t2.start();
}
您唯一的保证是,当且仅当 t2
打印某些内容时,它始终是 100
。这是因为 t2
已经 看过 一个 volatile write
到 a
。发生这种情况是因为已经建立了一个“先于发生”,从写入线程到读取线程,并且在 a = 10
之前完成的每个动作都保证对已经看到 a
的线程可见10
.
Could you explain yourself a bit further on "happens-before"?
关于“之前发生”最重要的是要记住它是 transitive relation。这意味着,如果 Java 语言规范 (JLS) 承诺 A“发生在”B 之前,并且它承诺 B“发生在”C 之前,那么您可以推断出 A“发生在”C 之前的承诺。
JLS 表示写入某些 volatile
变量“发生在”随后读取同一变量之前。
好吧!听起来很明显,不是吗?
但这不显而易见,因为 JLS 不 为非易失性变量提供相同的保证。如果处理器 A 将值 7 写入非易失性 int,然后一段时间后处理器 B 写入 5,则 JLS 不保证一段时间后变量的最终值为 5。处理器 B 将看到 5 (这是一个不同的“发生在之前”的承诺,见下文)。处理器 A 可以看到 5 或 7,任何其他处理器可以看到 5 或 7 或变量最初具有的任何值(例如 0)。
volatile
承诺如何提供帮助
假设我们有
volatile boolean flag = false;
/*non-volatile*/ int i = 0;
假设线程 A 这样做:
i = 7;
flag = true;
假设线程 B 这样做:
if (flag) {
System.out.println(i);
}
else {
System.out.println("Bleah!");
}
线程 B 可以打印“7”,也可以打印“Bleah!”但由于“先于发生”保证,我们绝对知道线程 B 永远不会打印“0”。为什么不呢?
线程 A 在设置 flag = true
之前设置 i = 7
。 JLS 保证如果单个线程在执行第二条语句之前执行一条语句,那么第一条语句“发生在”第二条语句之前。 (这听起来 惊人地 显而易见,但同样,它不应该。很多与线程有关的事情 并不 显而易见。)
线程 B 在打印 i
之前测试 flag
。所以*IF*线程A之前设置了flag=true
那么我们知道i
一定等于7
:传递性:i=7
”发生在“ flag=true
,以及对 volatile flag
的写入, 如果它发生了 ,“发生在”读取相同的 flag
。
如果真的发生了
数据竞争和竞争条件
要记住的最重要的事情是,当 JLS 承诺 A“发生在”B 之前时,他们并不是说 A 总是确实发生在 B 之前:他们是说你可以依赖于这种传递关系。他们说 如果 A 确实发生在 B 之前,那么所有“发生在”A 之前的事情也一定确实发生在 B 之前。
程序可以打印“Bleah!”因为没有什么能阻止线程 B 在线程 A 设置它之前测试 flag
。有人称之为“数据竞赛”。两个线程正在“竞赛”,看哪个线程先到达 flag
,程序的结果取决于哪个线程赢得比赛。
当程序的正确性取决于数据竞争的结果时,我们中的一些人称之为“竞争条件”,这确实令人头疼。无法保证具有竞争条件的程序在您的测试期间不会做一千次正确的事情,然后在对您的客户最重要的时候做错误的事情。