竞态条件定义
竞态条件会使得结果变得不可靠。当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。
最常见的竞态条件为,“先检查后执行(Check-Then-Act )”即通过一个可能失效的观测结果决定下一步动作。
代码示例
/**
* @author eventime
* 竞态条件错误演示代码
*/
public class RaceCondition {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 10个线程执行同一个操作,count增1
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(() -> count ++);
thread.start();
}
Thread.sleep(5000);
// 线程执行完毕后输出最终的count值
System.out.println(count);
}
}
运行五次代码得到如下结果:
9999,9999,10000,10000,10000
代码中我们创建了10000个线程对count进行 +1 操作。最终理想的情况下,count的值应该为10000;
但是前两次结果都出现了9999,这说明我们的代码并发出现了问题。
代码错误分析
有编程基础的读者应该知道代码** count ++** 其实是** count = count + 1,这里的操作并不是一个原子操作,而是由三个步骤组成。**
- 读取count
- 使count增1
- 写回count
这样也就造成了上文代码所出现的竞态条件问题。
从图中我们可以明显的看到错误所在的地方,即在线程1还未将count写回之前,线程2便读取了count。两线程都是在count = 0的基础上对count进行操作,这也就导致了最后的结果错误,两个线程进行了相同的工作。
线程2对count的操作依据的是一个可能过时的count。即通过一个可能失效的观测结果决定下一步动作。
解决办法
通过以上分析,我们发现,如果使得线程操作时一直保证count不失效, 那么就能避免这个问题;
/**
* @author eventime
* 竞态条件改进演示代码
*/
public class RaceCondition {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 10个线程执行同一个操作,count增1
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(() -> {
synchronized (RaceCondition.class) { // 锁定类对象
count++;
}
});
thread.start();
}
Thread.sleep(5000);
// 线程执行完毕后输出最终的count值
System.out.println(count);
}
}
我们通过对类上锁使得同一时间只有一个线程能够访问count,也就是说无法count在一个线程操作时候不用担心失效问题。