什么是Race Condition
首先,什么是Race Condition呢,Race Condition中文翻译是竞争条件,是指多个进程或者线程并发访问和操作同一数据且执行结果与访问发生的特定顺序有关的现象。换句话说,就是线程或进程之间访问数据的先后顺序决定了数据修改的结果,这种现象在多线程编程中是经常见到的。
Race Condition 实例
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">class MyThread implements Runnable {<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*** 计算类型,1表示减法,其他的表示加法*/</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> type;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-title" style="box-sizing: border-box;">MyThread</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> type) {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>.type = type;}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">run</span>() {<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (type == <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>)<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>; i < <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10000</span>; i++)Test.num--;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>; i < <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10000</span>; i++)Test.num++;}
}<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Test</span> {</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> num = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1000000</span>;<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">main</span>(String[] args) {Thread a = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Thread(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> MyThread(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>));Thread b = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Thread(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> MyThread(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>));a.start();b.start();<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** 主线程等待子线程完成,然后再打印数值*/</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">try</span> {a.join();b.join();} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">catch</span> (Exception e) {e.printStackTrace();}System.out.println(num);}}
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li></ul>
上边的程序非常简单。就是用两个线程同时操作和修改同一个数据,一个线程希望把num加10000,另一个线程希望把num减小10000,根据我们的计算,一个数加上10000,再减去10000,相当于没有变化,所以结果应该等于他自身。但是这只是我们的想象,事实真的是这样吗?大家可以运行一下上边的程序,你会吃惊的发现,结果并不是1000000,再多运行几次,是不是每次的结果都不一样。
没错,这就是产生了Race Condition,有两个线程,同时操作num这个变量,并且操作的结果与访问的顺序有关(这个不是我们控制的,而是由操作系统控制的,我们只能看到每次的结果都不一样)。
Race Condition 产生的原因
现在我们已经看到了Race Condition现象了,那么它是怎么产生的呢?先不要急切的说,你不是说了吗,是由于访问顺序不同造成的。我想说,学习新知识一定要追根溯源,要真正的弄明白,那么在硬件或者操作系统层面上,这一现象发生的原因是什么呢?
首先,我们来看一张图。
我们知道,在操作系统中,操作系统程序为每个线程分配了单独的寄存器和程序计数器。在上边的图中,我一共分成了三列,其中第一列表示线程一的操作过程,第二列表示线程二的操作过程,第三列表示内存中的结果。图中前两列的写着数字的小方框表示该线程所使用的寄存器,数字表示该寄存器中的值。第三列的小方框表示内存中的 一个存储单元,数值表示内存中存储的数值。
现在,我们来看一下操作的过程。(这里是简化了的示意过程,真正的过程要复杂的多)开始时,数据都是放在内存中的,所以通过LOAD指令,将num加载到寄存器中,接着执行相应的操作指令,这里分别为ADD(加1) SUB(减1)指令,指令执行结束后,结果是存储在相应的寄存器中的,这时内存中的数值还没有发生变化。最后执行STORE指令之后,寄存器中的数值被存储到内存中。
小方框从上到下的顺序表示线程1和线程2交替执行的过程。首先,线程1读取了内存中num的值,然后换到线程2执行,先读取了内存中num的数值,然后执行减1操作,最后将结果写回内存中,这时内存中的数据变成了999999,但是这一个变化线程1是看不到的,因为这一变化发生在线程1读取num的值之后。其实这时线程1读取到的数据已经是不正确的数据了,这是产生Race Condition的根本原因。然后线程1接着执行未执行完的指令,加1操作,最后将1000001写会内存,这是产生Race Condition的直接原因,它将线程2的结果给覆盖掉了。
这就是Race Condition产生的原因,大家是不是真正的明白了呢?。
当然Race Condition这种现象是不好的,所以我们会通过各种途径来避免产生Race Condition。