如果多个线程尝试更新同一个成员变量,就会发生竞态条件。但我更想知道如果我们不通过使其同步或使用其他方法在代码中处理它,JVM 如何在内部处理它?它会挂起我的程序吗?JVM 会如何对其作出反应?我认为 JVM 会暂时为此情况创建一个同步块,但我不确定具体会发生什么。
如果有人有一些见解,那就好了。
如果多个线程尝试更新同一个成员变量,就会发生竞态条件。但我更想知道如果我们不通过使其同步或使用其他方法在代码中处理它,JVM 如何在内部处理它?它会挂起我的程序吗?JVM 会如何对其作出反应?我认为 JVM 会暂时为此情况创建一个同步块,但我不确定具体会发生什么。
如果有人有一些见解,那就好了。
准确的术语是 数据竞争,它是 竞态条件 这一概念的一个特例。术语 数据竞争 是一个正式、明确定义的概念,这意味着它来自对代码的 形式化 分析。
获取真实情况的唯一方法是去学习 Java 语言规范中的内存模型章节,但这只是一个简化的观点:每当你有数据竞争时,几乎没有任何保证结果是什么,读取线程可能会看到变量曾经写入过的任何值。其中也包含唯一的保证:该线程将 不会 观察到“空气中出现”的值,即从未被写入的值。嗯,除非你处理的是 long
或 double
,那么你可能会看到断裂的写入。
synchronized
块),它们不可能出现。 - Marko Topolnikvolatile
,因为英特尔保证CPU缓存一致性。在其他CPU上,您只需要发出适当的内存屏障指令,然后处理一致性即可。如果您对这些细节感兴趣,我可以向您推荐这里:http://gee.cs.oswego.edu/dl/jmm/cookbook.html - Marko Topolnik请注意,我对较低层次的东西不是很熟悉,因此也许我并没有完全理解您的问题的深度。
i = 1
),其他所有操作都可能被另一个线程中断,但JVM无法知道这不是您代码预期的效果。(此外,这也是synchronized
的作用)。 - njzk2ordering: if a write is visible, so are any writes preceding it. For instance, if one thread executes:
x = new FancyObject();
another thread can read x
only after the constructor of FancyObject
has executed completely.
x
的写入,而没有看到逻辑上先于x
写入的构造函数的效果。如果不能做出这样基本的假设,则程序很可能不正确。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()
。有危险他们都会看到i为0,然后都将其增加1并保存结果。因此,在两次调用结束时,i只有1,而不是2!
这就是代码中的竞态条件问题,但还存在其他关于内存可见性的问题。当线程A更改共享变量时,没有保证(没有同步)线程B会看到这些更改!
因此,线程A可能会将i增加100次,一个小时后,调用getInt()的线程B可能会将i视为0、100或任何介于两者之间的值!
如果您正在研究Java并发编程,唯一明智的做法是阅读Brian Goetz等人的《Java并发编程实践》(好吧,可能还有其他学习方法,但这是一本由Joshua Bloch、Doug Lea和其他人共同撰写的优秀书籍)。