并发编程实战(2):原子性、可见性和竞态条件与复合操作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wen_fei/article/details/84111724

原子性

一个不可分割的操作,比如a=0;再比如:a++; 这个操作实际是a = a + 1;是可分割的,它其实包含三个独立的操作:读取a的值,将值加1,然后将计算结果写入a,这是一个“读取-修改-写入”的操作序列,所以他不是一个原子操作。

可见性

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。

比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就这这个操作同样存在线程安全问题。

关系

原子性是说一个操作是否可分割。可见性是说操作结果其他线程是否可见。

竞态条件

在并发编程中,由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,被称为竞态条件(race condition)

最常见的竞态条件:先检查后执行(Check-Then-Act),即通过一个可能失效的观测结果来决定下一步的动作:首先观察到某个条件为真(例如文件X不存在),然后根据这个观察结果采用相应的动作(创建文件X),但事实上在观察到这个结果以及开始创建文件之前,观察结果可能变得无效(另一个线程在这期间创建了文件X),从而导致各种问题(未预期的异常、数据被覆盖、文件被破坏等)。

最常见的竞态条件:延迟初始化,比如检查到某个实例为null,然后初始化实例

@NotThreadSafe
public class LazyInitRace {

	private ExpensiveObject instance = null;

	public ExpensiveObject getInstance() {
		if (instance == null) {
			instance = new ExpensiveObject();
		}
		return instance;
	}
}

另一种竞态条件: “读取-修改-写入”操作(例如递增一个计数器)

基于对象之前的状态来定义对象状态的转换

复合操作

要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前火之后读取和修改状态,而不是在修改状态的过程中。

一般将“先检查后执行”、“读取-修改-写入”等操作统称为复合操作:包含了一组以原子方式执行的操作以确保线程安全性。

猜你喜欢

转载自blog.csdn.net/wen_fei/article/details/84111724
今日推荐