如果多个线程尝试更新同一个成员变量,则称其为竞争条件。但是我更感兴趣的是,如果我们不通过使它同步或其他东西在我们的代码中处理它,JVM如何在内部处理它?它会挂起我的程序吗? JVM将如何应对?我认为JVM会暂时为这种情况创建一个同步块,但我不确定究竟会发生什么。
如果你们有任何见解,那就很高兴知道。
答案 0 :(得分:17)
准确的术语是数据竞赛,它是竞争条件的一般概念的特化。术语数据竞赛是一个官方的,精确指定的概念,这意味着它来自对代码的形式分析。
获得真实情况的唯一方法是去学习Java语言规范的内存模型章节,但这是一个简化的视图:每当你进行数据竞争时,几乎不能保证结果和读取线程可以看到任何已写入变量的值。其中还有唯一的保证:线程将不观察到“超薄空气”值,这是从未写过的。好吧,除非你正在处理long
或double
s,否则你可能会看到撕裂的写作。
答案 1 :(得分:5)
也许我错过了什么,但有什么可以处理?还有一个线程会先到达那里。根据哪个线程,该线程将只更新/读取某个变量并继续执行下一条指令。它不能神奇地构建一个同步块,它并不真正知道你想做什么。换句话说,所发生的事情将取决于“种族”的结果。
注意我并没有深入到较低级别的东西,所以也许我不完全理解你的问题的深度。
答案 2 :(得分:2)
Java提供synchronized
和volatile
来处理这些情况。正确使用它们可能会令人沮丧,但请记住,Java只暴露了现代CPU和内存架构的复杂性。替代方案是始终谨慎对待,有效地同步所有会破坏性能的东西;或忽略问题,并提供任何线程安全。幸运的是,Java在java.util.concurrent
包中提供了出色的高级构造,因此您可以经常避免处理低级别的东西。
答案 3 :(得分:1)
简而言之,JVM 假设代码在将其转换为机器代码时没有数据争用。也就是说,如果代码未正确同步,则Java语言规范仅提供有关该代码行为的有限保证。
大多数现代硬件同样假设代码在执行时没有数据竞争。也就是说,如果代码未正确同步,则硬件仅对其执行结果提供有限保证。
特别是Java语言规范guarantees以下仅在没有数据竞争的情况下:
排序:如果写入是可见的,那么它之前的任何写入都是可见的。例如,如果一个线程执行:
x = new FancyObject();
只有x
的构造函数完全执行后,另一个帖子才能读取FancyObject
。
在数据竞争的情况下,这些保证是无效的。读取线程可能永远不会看到写入。也可以看到x
的写入,而不会看到逻辑上先于x
写入的构造函数的影响。如果不能做出这样的基本假设,那么该计划是不正确的。
然而,数据竞争不会损害Java虚拟机的完整性。特别是,JVM不会崩溃或停止,并且仍然保证内存安全(即防止内存损坏)和certain semantics of final fields。
答案 4 :(得分:0)
JVM会很好地处理这种情况(即它不会挂起或抱怨),但你可能得不到你喜欢的结果!
当涉及多个线程时,java变得非常复杂,甚至看起来非常正确的代码也会被严重破坏。举个例子:
public class IntCounter {
private int i;
public IntCounter(int i){
this.i = i;
}
public void incrementInt(){
i++;
}
public int getInt(){
return i;
}
}
在很多方面存在缺陷。
首先,假设i当前为0,并且线程A和线程B几乎同时调用incrementInt()
。有一种危险,他们都会看到我是0,然后两者都增加1然后保存结果。所以在两次通话结束时,我只有1,而不是2!
这是代码的竞争条件问题,但是还有其他与内存可见性有关的问题。当线程A更改共享变量时,无法保证(没有同步)线程B将看到更改!
因此,线程A可以增加100倍,一小时后,线程B,调用getInt(),可能会将i视为0,或100或其中任何位置!
如果你正在深入研究java并发性,唯一能做的就是阅读Brian Goetz等人的Java Concurrency in Practice。 (好吧,可能有其他好方法可以了解它,但这是一本由Joshua Bloch,Doug Lea和其他人撰写的好书)