1. 目标:
从原码层面分析CAS算法、以及java.util.concurrent.atomic 包下的原子类是如何运用CAS算法而实现线程安全。
2. 基础知识
CAS算法基本原理
CAS算法全称:compare and swap (比较并交换),是CPU指令级的操作,只有一步原子操作,整个操作是原子的,也就是要么不执行,要么执行完。这样的系统原子操作能做什么呢?比较内存中的参数值和方法调用处提供的参数值,如果相等,则将内存的参数值,设置为新值,否则返回内存中的参数值。
volatile 关键字
这个关键字修饰的变量,能保证变量在线程之间可见。再解释下,如果2个线程同时操作一个被volatile关键字修饰的变量,各个线程都会将变量从内存统一拷贝到自己的所在cpu的缓存中,当一个线程对变量进行修改之后,该变量位于其他线程的cpu缓存中拷贝能立即感知到。并同时调整为一致。
Unsafe 类
大家都知道java一般很少直接操作内存,但是其实还是有一个类是可以直接操作修改内存的,他就是Unsafe。要获取一个对象的某个字段的值是多少,我们应该知道几个重要参数,第一个是对象在内存中的起始位置,第二个是需要获取的字段在内存中的相对地址,第三个是字段的长度,也就是需要取几个字节,才对应这个字段的值。
3. 从源码角度分析AtomicInteger是如何实现线程安全的
AtomicInteger的成员变量介绍
private static final Unsafe unsafe = Unsafe.getUnsafe();//用于操作内存的Unsafe实例
private static final long valueOffset;//AtomicInteger所保存的int值在内存中的偏移量
private volatile int value; //AtomicInteger中保存的int值,被volatile关键字修饰,确保线程之间可见。
static {
try {
//实际设置了int值在内存中的偏移
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
下面是我们征对常用的方法进行介绍
//在当前值的基础上自动+1,并且返回新值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
这里我们看底层是调用了unsafe的getAndAddInt
方法,我们跟进去
//obj 是AtomicInteger对象
//index long型的内存偏移量
//addVale 本次操作需要增加的值
public final int getAndAddInt(Object obj, long index, int addVale) {
int oldValue;
do {
oldValue = this.getIntVolatile(obj, index);//先从内存里取的参数的值
} while(!this.compareAndSwapInt(obj, index, oldValue, oldValue + addVale));
return oldValue;//返回老值
}
//这个方法被final标记了,说明是一个本地方法,我们从openjdk里面把相应的c语言代码拿出来,主要要记住,下面的compareAndSwapInt方法是一个原子操作。
public final native boolean compareAndSwapInt(Object obj, long index, int oldValue, int newValue);
int compare_and_swap (int* reg, int oldval, int newval)
{
ATOMIC();
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
END_ATOMIC();
return old_reg_val;
}
简单分析上面的代码,线程1和线程2并行修改值,一开始大家拿到的值都是55,线程1希望+1,改为56,线程2也希望+1,但是如果在线程1先+1的情况下,线程再在55的基础上+1,就没有意义了,必须要在56的基础上+1。我们逐行分析下代码。
CAS算法缺点
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
- 循环时间长开销很大。
- 只能保证一个共享变量的原子操作。
- ABA问题