原子包java.util.concurrent.atomic使用了CAS
用一个小例子看一下atomic类的线程安全的功能:
public class AtomicTest {
private static int unsafeInt = 0;
private static AtomicInteger safeInt = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
CountDownLatch count = new CountDownLatch(10);
for(int i=0; i<10; i++) {
new Thread(new Runnable() {//启10个线程,每个线程累加1000次
@Override
public void run() {
for(int i=0; i<1000; i++) {
unsafeInt++;
safeInt.getAndAdd(1);
}
count.countDown();
}
}).start();
}
count.await();
System.out.println("unsafeInt:" + unsafeInt);
System.out.println("safeInt:" + safeInt.get());
}
}
运行结果
unsafeInt:8802
safeInt:10000
理解CAS
来看看juc包中的atomic包,AtomicInteger的getAndAdd方法:
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
其中delta=1,而valueOffset是AtomicInteger类静态成员变量,static初始化:
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
用unsafe.objectFieldOffset(Field field)方法获取的当前实例的value这个Field的内存偏移量。
unsafe类:
//1.8新增,给定对象o,根据获取内存偏移量指向的字段,将其增加delta,
//这是一个CAS操作过程,直到设置成功方能退出循环,返回旧值
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
//获取内存中最新值
v = getIntVolatile(o, offset);
//CAS操作
} while (!compareAndSwapInt(o, offset, v, v + delta)); //锁自旋
return v;
}
CAS本体来了:
compareAndSwapInt(o, offset, v, v + delta)
"o对象偏移量为offset位置开始的int值,将其值设为v+delta,设置时期望其值是v;成功则返回true,否则返回false",这是这个方法的含义。
这是个native方法,并且是个所谓CPU原语。由硬件指令保证这个操作是原子性的。Java程序通过JNI来完成CPU指令的操作:cmpxchg
CAS操作属于非阻塞算法,而且没有使用锁,相比synchronized这种阻塞算法加锁,由于一般认为CPU切换时间比CPU指令集操作更加耗费时长,所以CAS比加锁性能更优。(这里指的是所谓的重型锁,也就是操作系统通过硬件机制加的锁)
CAS 操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。 当多个线程同时使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS 操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
总结
关于CAS是否有上下文、用户态内核态切换?以及CAS是否是系统调用?
CAS不走系统调用,是在用户态的代码中“插入”cmpxchg汇编指令,由这种CPU原语性质的汇编指令保证操作的原子性,而没有走入内核的代码、当然也就没有上下文、用户态内核态的切换。
而重量级锁才是进行了系统调用、用户态代码发起系统调用向内核申请mutex互斥量,CPU接着执行进入内核代码、内核代码进行了后续的利用高低电位来锁总线等一系列操作,最后互斥的设置了一个内存中的标志位称为互斥锁,然后把这个锁返回给了用户态代码。这个过程中当然是有上下文/用户态内核态切换的。
后记
CAS最早是在2012年翻译Disruptor原理的英文论文的时候了解到的,当时真的跟看天书差不多,朦朦胧胧留下了点印象,时隔多年,理解逐渐清晰。不怕晚,古人云,朝闻道,夕死可矣。