简体   繁体   English

JVM如何在内部处理竞争条件?

[英]How does the JVM internally handle race conditions?

If multiple threads try to update the same member variable, it is called a race condition. 如果多个线程尝试更新相同的成员变量,则称其为竞争条件。 But I was more interested in knowing how the JVM handles it internally if we don't handle it in our code by making it synchronised or something else? 但是我更感兴趣的是,如果我们不通过使它同步或其他东西在我们的代码中处理它,JVM如何在内部处理它? Will it hang my program? 它会挂起我的程序吗? How will the JVM react to it? JVM将如何应对? I thought the JVM would temporarily create a sync block for this situation, but I'm not sure what exactly would be happening. 我认为JVM会暂时为这种情况创建一个同步块,但我不确定究竟会发生什么。

If any of you have some insight, it would be good to know. 如果你们中的任何人有一些见解,那么最好知道。

The precise term is a data race , which is a specialization of the general concept of a race condition . 精确的术语是数据竞赛 ,它是竞争条件的一般概念的专业化。 The term data race is an official, precisely specified concept, which means that it arises from a formal analysis of the code. 数据竞争一词是一个官方的,精确指定的概念,这意味着它来自对代码的正式分析。

The only way to get the real picture is to go and study the Memory Model chapter of the Java Language Specification, but this is a simplified view: whenever you have a data race, there is almost no guarantee as to the outcome and a reading thread may see any value which has ever been written to the variable. 获得真实情况的唯一方法是去学习Java语言规范的内存模型章节,但这是一个简化的视图:每当你有数据竞争时,几乎不能保证结果和阅读线程可能会看到任何已写入变量的值。 Therein also lies the only guarantee: the thread will not observe an "out-of-thin-air" value, such which was never written. 其中还有唯一的保证:线程不会观察到“超薄空气”值,这种值从未写过。 Well, unless you're dealing with long s or double s, then you may see torn writes. 好吧,除非你处理longdouble ,否则你可能会看到撕裂的写作。

Maybe I'm missing something but what is there to handle? 也许我错过了什么,但有什么可以处理? There is still a thread that will get there first. 还有一个线程会先到达那里。 Depending on which thread that is, that thread will just update/read some variable and proceed to the next instruction. 根据哪个线程,该线程将只更新/读取某个变量并继续执行下一条指令。 It can't magically construct a sync block, it doesn't really know what you want to do. 它不能神奇地构建一个同步块,它并不真正知道你想做什么。 So in other words what happens will depend on the outcome of the 'race'. 换句话说,所发生的事情将取决于“种族”的结果。

Note I'm not heavily into the lower level stuff so perhaps I don't fully understand the depth of your question. 注意我并没有深入到较低级别的东西,所以也许我不完全理解你的问题的深度。

Java provides synchronized and volatile to deal with these situations. Java提供了synchronizedvolatile来处理这些情况。 Using them properly can be frustratingly difficult, but keep in mind that Java is only exposing the complexity of modern CPU and memory architectures. 正确使用它们可能会令人沮丧,但请记住,Java只暴露了现代CPU和内存架构的复杂性。 The alternatives would be to always err on the side of caution, effectively synchronizing everything which would kill performance; 替代方案是始终谨慎对待,有效地同步所有会破坏性能的东西; or ignore the problem and offer no thread safety whatsoever. 或忽略问题,并提供任何线程安全。 And fortunately, Java provides excellent high-level constructs in the java.util.concurrent package, so you can often avoid dealing with the low-level stuff. 幸运的是,Java在java.util.concurrent包中提供了出色的高级构造,因此您通常可以避免处理低级别的东西。

In short, the JVM assumes that code is free of data races when translating it into machine code. 简而言之,JVM 假定代码在将其转换为机器代码时没有数据争用。 That is, if code is not correctly synchronized, the Java Language Specification provides only limited guarantees about the behavior of that code. 也就是说,如果代码未正确同步,则Java语言规范仅提供有关该代码行为的有限保证。

Most modern hardware likewise assumes that code is free of data races when executing it. 大多数现代硬件同样假设代码在执行时没有数据竞争。 That is, if code is not correctly synchronized, the hardware makes only limited guarantees about the result of its execution. 也就是说,如果代码未正确同步,则硬件仅对其执行结果提供有限保证。

In particular, the Java Language Specification guarantees the following only in the absence of a data race: 特别是,Java语言规范仅在没有数据竞争的情况下保证以下内容:

  • visibility : reading a field yields the value last assigned to it (it is unclear which write was last , and writes of long or double variables need not be atomic ) 可见性 :读取字段会产生最后分配给它的值(不清楚哪个写入是最后一个 ,并且长或双变量的写入不必是原子的
  • ordering : 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. 另一个线程只有在FancyObject的构造函数完全执行后才能读取x

In the presence of a data race, these guarantees are null and void. 在数据竞争的情况下,这些保证是无效的。 It is possible for a reading thread to never see a write. 读取线程可能永远不会看到写入。 It is also possible to see the write of x , without seeing the effect of the constructor that logically preceded the write of x . 也可以看到x的写入,而不会看到逻辑上先写入x的构造函数的影响。 It is very unlikely that the program is correct if such basic assumptions can not be made. 如果不能做出这样的基本假设,那么该计划是不正确的。

A data race will however not compromise the integrity of the Java Virtual Machine. 但是,数据竞争不会损害Java虚拟机的完整性。 In particular, the JVM will not crash or halt, and still guarantee memory safety (ie prevent memory corruption) and certain semantics of final fields . 特别是,JVM不会崩溃或停止,并且仍然保证内存安全(即防止内存损坏)和最终字段的某些语义

The JVM will handle the situation just fine (ie it will not hang or complain), but you may not get a result that you like! JVM会很好地处理这种情况(即它不会挂起或抱怨),但你可能得不到你喜欢的结果!

When multiple threads are involved, java becomes fiendishly complicated and even code that looks obviously correct can turn out to be horribly broken. 当涉及多个线程时,java会变得非常复杂,甚至看起来非常正确的代码也会变得非常糟糕。 As an example: 举个例子:

public class IntCounter {
    private int i;

    public IntCounter(int i){
         this.i = i;
    }

    public void incrementInt(){
        i++;
    }

    public int getInt(){
        return i;
    }
}

is flawed in many ways. 在很多方面存在缺陷。

First, let's say that i is currently 0 and thread A and thread B both call incrementInt() at about the same time. 首先,假设我当前为0,并且线程A和线程B几乎同时调用incrementInt() There is a danger that they will both see that i is 0, then both increment it 1 and then save the result. 有一种危险,他们都会看到我是0,然后两者都增加1然后保存结果。 So at the end of the two calls, i is only 1, not 2! 所以在两次通话结束时,我只有1,而不是2!

That's the race condition problem with the code, but there are other problems concerning memory visibility. 这是代码的竞争条件问题,但是存在关于内存可见性的其他问题。 When thread A changes a shared variable, there is no guarantee (without synchronization) that thread B will ever see the changes! 当线程A更改共享变量时,无法保证(没有同步)线程B将看到更改!

So thread A could increment i 100 times, and an hour later, thread B, calling getInt(), might see i as 0, or 100 or anywhere in between! 因此,线程A可以增加100倍,一小时后,线程B,调用getInt(),可能会将i视为0,或100或其间的任何位置!

The only sane thing to do if you are delving into java concurrency is to read Java Concurrency in Practice by Brian Goetz et al. 如果你正在深入研究java并发性,唯一能做的就是阅读Brian Goetz等人的Java Concurrency in Practice。 (OK there's probably other good ways to learn about it, but this is a great book co written by Joshua Bloch, Doug Lea and others) (好吧,可能还有其他很好的方法可以了解它,但这是一本很好的书,由Joshua Bloch,Doug Lea和其他人撰写)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM