引入CAS(compareAndSwap)的原因
CAS即compareAndSwap(比较和交换),引入CAS的主要原因是解决锁带来的性能影响,我们知道在Java中,为了保证变量的原子性,Java提供了一些 锁机制,比如synchronized
,Lock
等,锁是能够保证变量的原子性,其原理是阻塞线程,当某一个线程正在执行任务时,如果任务加了锁,那么其他线程 进入任务时会被阻塞,这样会导致性能变得很差,在高并发的场景下时极度不推荐加锁操作的,所以引入了CAS。
CAS原理
CAS是JDK提供的原子操作类,它提供了硬件级别的隔离,可以使用Unsafe
来操作CAS,Unsafe类下提供了compareAndSwap*
操作,我们看出compareAndSwap*
这些方法都是native(本地方法)。
public final native boolean compareAndSwapObject(Object obj, long valueOffset, Object expect, Object update);
public final native boolean compareAndSwapInt(Object obj, long valueOffset, Object expect, Object update);
public final native boolean compareAndSwapLong(Object obj, long valueOffset, Object expect, Object update);
compareAndSwap*
本地方法有四个参数,第一个object为对象的内存地址,valueOffset代理对象在内存中的偏移量,expect为期望值,update为新值 ,连贯起来解释这个方法的意思,如果对象obj内存偏移量为valueOffset的值等于期望值expect,那么使用新值update替换期望值expect,反之则不做任何操作
。
CAS ABA问题
当线程1使用CAS修改变量A的值为X,在执行CAS操作之前,线程1先获取变量A,这时线程2获取变量A,然后使用CAS操作更新A为Y,然后又将A更新为X,这时虽然A依然为X, 但是已经不是线程1的那个X了,可能说得有点抽象,那么我们就用一个简单且通俗的经历来说,我们小时候和小伙伴一起玩耍,小伙伴借了我一个玩具,然后后面我给他弄丢了, 突然有一天我们闹矛盾了,然后他叫我还他玩具,我于是买了一个一模一样的给他,但是他却不要,他说:"我不要你这个,我就要我的那个","我就要我的那个其实上升为一个哲学问题了", 那么ABA问题其实就是这个意思,虽然值一模一样,但是已经不是以前那个啦。
解决ABA问题
版本号
解决ABA问题我们可以加一个版本号,其实就是乐观锁的方式,每次更新操作版本号加1 比如线程1需要更新变量A,它会先获取变量A的值和版本号,如图,线程1获取到的版本号为1,此时线程2也获取到A,对其进行更新操作,version 1,此时version=2, 那么这个时候线程1对便两个A进行更新,发现版本和自己自己最初获取的版本号不一样,则证明变量A被修改过,那么就不对其进行更新。
AtomicStampedReference
AtomicStampedReference给每个变量的状态值加上一个时间戳,从来避免ABA问题。
下面我们演示一下ABA问题以及使用AtomicStampedReference解决ABA问题。
使用AtomicInteger
threadA使用compareAndSet
对变量atomicInteger进行两次更新,然后threadB对atomicInteger进行更新,输入为true,显然,这是不对的。
public class CasTest {
static AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
atomicInteger.compareAndSet(100, 200);
atomicInteger.compareAndSet(200, 100);
}
});
Thread threadB = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(2000);
boolean b = atomicInteger.compareAndSet(100, 200);
System.out.println(b);
}
});
threadA.start();
threadB.start();
}
}
使用AtomicStampedReference
使用AtomicStampedReference,每次更新操作都会加上一个时间戳,于是输出为false,因为变量已经不是以前那个变量了,虽然值是一样的
代码语言:javascript复制public class CasAtomicStampedReferenceTest {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,0);
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
atomicStampedReference.compareAndSet(100, 200, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() 1);
atomicStampedReference.compareAndSet(200, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() 1);
}
});
Thread thread1 = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
Thread.sleep(2000);
boolean b = atomicStampedReference.compareAndSet(100, 200, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() 1);
System.out.println(b);
}
});
thread.start();
thread1.start();
}
}
关于CAS,我们分享到这里,感谢你的观看,我们下期见!