CAS:即compare and swap(比较并替换),CAS需要解决的是多线程并发时线程间切换时间片导致的原子性问题。
我们先看一段代码
public class CasDemo1 {
static int k = 0;
public static void main(String[] args) {
for(int i = 0;i<10;i++) {
Thread t = new Thread(() -> {
//每个线程让count自增100次
try {
// 等待所有线程都起来
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 100; j++) {
k++;
}
});
t.start();
}
try {
// 阻塞2秒,等待线程执行结束
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(k);
}
}
十个线程,同时让k的值+100,但是结果输出的值是小于等于1000的,这是为什么呢?
原因在于k++这个操作在CPU层面并不保证原子性,会被拆分为
1、查询k的值
2、k=k+1
此时由于线程间会存在时间片的切换,即假如线程A执行到第一步,查询k的值=0,此时CPU进行了切换,线程B成功的执行了1和2,此时对于线程B来说K=1,此时时间片切换到A线程,A线程中的k=0,此时执行2,k+1=1,那么就和我们的预期不同了,并发问题就产生了
为了解决上述问题,我们可以使用加锁来避免并发,使用synchronized来加锁,但是synchronized是一种悲观锁的思想,性能会比较差
CAS采用的就是一种乐观锁的思想,即执行时假设没有并发问题,先比较,比较后发现没有被改动则替换,被改动了则自旋重新执行上述1和2步骤直到成功
java已经提供了多种支持并发的cas相关类,主要是在java.util.concurrent.atomic包下,如AtomicInteger、AtomicBoolean、AtomicLong等
我们就看一种AtomicInteger源码的实现
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
public AtomicInteger() {
}
/**
* Gets the current value.
*
* @return the current value
*/
public final int get() {
return value;
}
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
......
}
Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。如果我们必须要实现自己的基于Unsafe类的功能,则只能通过反射获取Unsafe类,因为Unsafe在创建时加了限制,只有rt.jar包下的类具有权限访问,从Unsafe的getUnsafe可以看出来
Unsafe为我们在Java中提供了直接操作内存的能力。
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField(“value”)),看到这个代码,可以看出来valueOffset的值是AtomicInteger中的value值在内存中的地址,即偏移量
unsafe.compareAndSwapInt(this, valueOffset, expect, update)
cas的核心操作,即查询参数1中内存地址为valueOffset的值,当等于参数3时修改为参数4,返回成功,当不相等时返回false
参数1:操作的实例
参数2:操作实例中内存地址
参数3:预期值
参数4:期望修改成的值
自增1并且返回
看getAndAddInt方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 从内存中读取当前对象地址为valueOffset的值
var5 = this.getIntVolatile(var1, var2);
// 自旋直到cas操作成功为止,当var5的值仍然是var5时修改为var5+var4
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
即最终会把AtomicInteger中的value+1,并且返回+1后的值
看完AtomicInteger的源码,我们基本上已经懂了cas的实现,即通过Unsafe类来直接比较在内存中的值,如果相同则替换,失败则自旋直到成功,通过直接比较内存中的值来解决上面时间片切换导致的原子性问题