原理
一种算法,一种CPU的特殊指令,这些特殊指令,由CPU保证了他们的原子性,其次,一个指令可以做多件事情,比如先比较,再更新。由于CPU保证了原子性,所以不会出现线程安全问题。
使用场景
并发
我认为V的值应该是A,如果是的话那我就把它改成B,如果不是A(说明被别人修改过了),那我就不修改了,避免多人同时修改导致出错。
等价代码
public class SimulatedCAS {
private volatile int value;
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
// 1. 先比较
if (oldValue == expectedValue) {
value = newValue;
}
return oldValue;
}
}
案例演示
两个线程竞争,其中一个落败
两个人邀请另一个人吃饭,同时只能一个人邀请成功,谁先邀请,谁成功的机率就大一些。
public class TwoThreadsCompetition extends Thread {
private volatile int value;
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
// 1. 先比较
if (oldValue == expectedValue) {
value = newValue;
System.out.println(Thread.currentThread().getName()+"修改成功");
}
return oldValue;
}
public static void main(String[] args) throws InterruptedException {
TwoThreadsCompetition r = new TwoThreadsCompetition();
r.value = 0;
Runnable target;
Thread t1 = new Thread(r, "Thread-1");
Thread t2 = new Thread(r, "Thread-2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(r.value);
}
@Override
public void run() {
compareAndSwap(0, 1);
}
}
结果
Thread-1修改成功
1
场景一
- 乐观锁
ConcurrentHashMap 源码中很典型的cas使用
- 并发容器
- 原子类
AtomicInteger加载Unsafe工具,用来直接操作内存数据,使用Unsafe来实现底层操作
用volatile修饰value字段,保证可见性
在静态代码块中引用value,同时value使用volatile修饰,保证可见性
在AtomicInteger数据定义的部分,我们还获取了unsafe实例,并且定义了valueOffset。再看到static块,懂类加载过程的都知道,static块的加载发生于类加载的时候,是最先初始化的,这时候我们调用unsafe的objectFieldOffset从Atomic类文件中获取value的偏移量,那么valueOffset其实就是记录value的偏移量的。
valueOffset表示的是变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的,这样我们就能通过unsafe来实现CAS了。value是用volatile修饰的,保证了多线程之间看到的value值是同一份。
我们看var5获取的是什么,通过调用unsafe的getIntVolatile(var1, var2),这是个native方法,其实就是获取var1中,var2偏移量处的值。var1就是AtomicInteger,var2就是我们前面提到的valueOffset,这样我们就从内存里获取到现在valueOffset处的值了。
现在重点来了,compareAndSwapInt(var1, var2, var5, var5 + var4)其实换成compareAndSwapInt(obj, offset, expect, update)比较清楚,意思就是如果obj内的value和expect相等,就证明没有其他线程改变过这个变量,那么就更新它为update,如果这一步的CAS没有成功,那就采用自旋的方式继续进行CAS操作。
Unsafe的getAndAddInt方法分析:自旋 + CAS,在这个过程中,通过compareAndSwapInt比较并更新value值,如果更新失败,重新获取,然后再次更新,直到更新成功。
Unsafe类
Unsafe是CAS的核心类。Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作。
缺点
- ABA问题
CAS只是判断值是否相等,而不是判断是否修改过,如:一个线程拿到一个值为1,然后进行计算,在这个过程中,来了一个线程,把1改成了2,紧接着又一个线程把数据改成1,而这时第一个线程,计算结束,判断值仍然为1,就认为数据在此期间没有被修改过,然后将值修改为对应的结果。而实际上该数据已经被修改多次了。
解决方法:添加版本号,每次通过对比版本号。而不是通过对比值
- 自旋时间过长
在=源码中可以看到,有些地方是进行死循环的,而再次期间如果竞争非常激烈,或者锁一直拿不到,那么对内存是很大的消耗