文章目录
  1. 1. 竞态条件
  2. 2. 寻找竞态条件
    1. 2.1. 检查并执行
    2. 2.2. 读取后更新,接着写入(计数器)

竞态条件

多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争。一个例子就是无序处理。

竞态条件是并发产生的 Bug,这些Bug 是多线程在同一时间并行执行产生的。

  • 发生时间

    两个线程在操作同一个对象,而该对象却没有任何同步措施,线程交替执行。

  • Eg

    计数器 自增。

    自增不是原子操作,被分成3步:

    1. read
    
    2. update
    
    3. write
    

    如果两个线程在同一时间都对计数器操作,它们read 到相同的值(线程交替执行,故可能发生),而此时一个线程在update,故计数会被丢失。

    而原子操作不会有上述问题,因为线程不会在原子操作间交替执行。

寻找竞态条件

一般来说,有两种模式易产生竟态条件:

  • 检查并执行

  • 读取后更新,接着写入(计数器)

检查并执行

  • Eg

    单例模式中 getInstance()

1
2
3
4
5
6
7
8
9
10
11
12
// 一个线程检查 _instance,发现其为null;
// 该线程开始初始化时(尤其耗时长,进入临界区),另一个线程也来检查,发现为null;
// 最终各自返回自己初始化的实例
public Singleton getInstance(){
if(_instance == null){ //race condition if two threads sees _instance= null
_instance = new Singleton();
}
}
/*
* 避免方法就是用 “synchronized ” 来上锁 ,使其成为原子操作
*/

读取后更新,接着写入(计数器)

  • Eg

    put if absent scenario(没有就添加)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// contains() 和 put() 均是原子操作;
// 但二者合在一起操作就不是原子操作了。
/*
* 思考:
* 线程1 检查了条件,进入{}被阻塞;
* cpu切换至线程2,线程2 检查了条件,也进入{}被阻塞;
* 那现在我们有俩个线程都进入了{},最后执行结果不可预料
*
*/
if(!hashtable.contains(key)){
hashtable.put(key,value);
}
//解决方法:“synchronized ”
文章目录
  1. 1. 竞态条件
  2. 2. 寻找竞态条件
    1. 2.1. 检查并执行
    2. 2.2. 读取后更新,接着写入(计数器)