当谈到cas解决多线程同步问题,看一个人有没有真正理解cas,不是听他说cas就是使用cpu机器指令比较前后值完成设置保证了原子性,这样基本就是百度百出来的,根本没有好好理解cas的真正过程
【cas要点】
JUC就是jdk并发包【目录简称】
以AtomicInteger内部源码举例,jdk1.8.0_202
AtomicInteger.java
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
unsafe.java
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
也有的显示源码是这样的
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
AtomicInteger会在类的字节码文件加载到jvm时,执行static静态块初始化,取得内部value
变量偏移长度
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
value是volatile类型,value就是AtomicInteger代表的值
private volatile int value;
然后主要是看cas循环怎么解决多线程变量同步问题,就是怎么解决 i++ 这样的问题的,这样才能真正理解cas的无锁同步机制
循环的过程主要是两步,可以理解为两个原子操作
1. 获取当前值【最新】值,volatile属性
int current = get();
或者
//var1是实例对象this,var2是valueOffset就是value的偏移长度,这样也可以得到value最新值
var5 = this.getIntVolatile(var1, var2);
2. 判断与修改值
含义:通过对象this与变量偏移地址valueOffset取得变量值
//如果与预期expect (就是上面的原子操作取得的值) 一致,说明这两步原子操作中间【没有】被【并发修改】,可以设置为update
//与预期expect不一致,进入下次循环。
//说明value被其他线程修改了,为了保证变量同步,应该重新获取一次最新值
//也就是当前线程的这两个原子操作中间被中断,线程挂起,被其他线程执行了cas设置
//通过对象this与变量偏移地址valueOffset可以获取最新被修改后的值 (volatile属性)(这是在cas内部处理)
//expect就是第一个原子操作取得的current值, 用于校验这两个原子操作是否被中断,从而保证【多线程单变量同步】
unsafe.compareAndSwapInt(this, valueOffset, expect, update)
或者
this.compareAndSwapInt(var1, var2, var5, var5 + var4)
从源码以及我上面的说明可以看出,cas自旋一次的过程是两个原子操作
CAS问题
即使使用多个cas设置多个volatile变量,但是不能保证这些多个volatile变量被同时在一起修改的原子性
没有原子性,就会出现并发问题,变量值不正确。
虽然CAS保证了两个原子操作的前后的【值】一样,但是不能保证这个值没有被重新设置为【一样】的值
比如这个值可能是某个链表的指针,虽然地址是一样,执行同一个地方,但是你不知道,在第一个原子操作后,线程挂起
其他线程修改了这个链表后,恰巧把相同的地址放在这里,等你第二个原子操作开始,虽然【值】是一样的
但是这个【新值】所指的含义,或者这个【新值】本身的含义不同于原来的【旧值】
从我上面的分析,看出,虽然cas无锁同步,保证了多线程下的单变量同步
但当需要cas同一变量的线程太多,因为同一时刻,只有一个线程可以cas成功
余下的线程都在循环cas,线程越多,争用越大,循环时间就长,消耗cpu时间