CAS定义:
CAS的全称是Compare-And-Swap,它是CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
CAS操作包含三个操作数一内存位置 (V)、期望值(A) 和新值(B)。
如果内存位置的值与期望值匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不作任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。(CAS在一些特殊情况下仅返回CAS是否成功,而不提取当前值) CAS有效的说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置的值,只告诉我这个位置现在的值即可。” 是一种不同于synchronized的乐观锁
案例:
用synchronized实现线程安全
public class CASDemo01 {
volatile static int count = 0;
public synchronized static void request() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(5);
count++;
}
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
int threadSize = 100;
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for (int i = 0; i < threadSize; i++) {
Thread thread = new Thread(() -> {
try {
for (int j = 0; j < 10; j++) {
request();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
});
thread.start();
}
countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ", 耗时:" + (endTime - startTime) + ",count = " + count);
}
}
结果: main, 耗时:6332,count = 1000
模拟CAS实现线程安全
public class SimulationCAS {
volatile static int count = 0;
public static void request() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(5);
int expectCount;
while (!compareAndSwap(expectCount = getCount(), expectCount + 1)) {
}
}
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
int threadSize = 100;
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for (int i = 0; i < threadSize; i++) {
Thread thread = new Thread(() -> {
try {
for (int j = 0; j < 10; j++) {
request();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
});
thread.start();
}
countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + ", 耗时:" + (endTime - startTime) + ",count = " + count);
}
public static synchronized boolean compareAndSwap(int expectCount, int newCount) {
if (getCount() == expectCount) {
count = newCount;
return true;
}
return false;
}
public static int getCount() {
return count;
}
}
结构:main, 耗时:205,count = 1000
Q:怎么使用JDK提供的CAS支持?
A: java中提供了对CAS操作的支持,具体在sun.misc.unsafe类中, 声明如下:public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
参数var1
:表示要操作的对象
参数var2
:表示要操作对象中属性地址的偏移量
参数var4
;表示需要修改数据的期望的值
参数var5
:表示需要修改为的新值
Q: CAS实现原理是什么?
A: CAS通过调用JNI的代码实现,JNI: java Native Interface, 允许java调用其它语言。而compareAndSwapxxx
系列的方法就是借助“C语言”来调用cpu底层指令实现的。以常用的Intel x86平台来说,最终映射到的cpu的指令为cmpxchg
,这是一个原子指令,cpu执行此命令时,实现比较并替换的操作!
Q:现代计算机动不动就上百核心,cmpxchg怎么保证多核心下的线程安全?
A: 系统底层进行CAS操作的时候,会判断当前系统是否为多核心系统,如果是就给“总线”加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行CAS操作,也就是说CAS的原子性是平台级别的!
Q:CAS的优缺点
A:
优点:
不加锁,性能比较好
缺点:
1.do while循环时间长的话开销大(如果比较不成功一直在循环,最差的情况,就是某个线程一直取到的值和预期值都不一样,这样就会无限循环)
2.只能保证一个共享变量的原子性
当对一个共享变量执行操作时,我们可以通过循环CAS的方式来保证原子操作,但是对于多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候只能用锁来保证原子性
3.ABA问问题
// ursafe.getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4){
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
}while(!this.compareAndSwapInt(varl, var2, var5,var5 + var4));
return var5;
}
Q:如何解决ABA问题?
A:解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它的版本号,CAS操作时都去对比此版本号。java中ABA解决方法(AtomicStampedReference
)AtomicStampedReference
主要包含一个对象引用及一个可以自动更新的整数“stamp?"的pair对象来解决ABA问题。
AtomicStampedReference里面维护了一个Pair对象, Pair里面封装了引用reference
和时间戳stamp
,就是简单的一层封装。
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference && //期望引用和当前引用一致
expectedStamp == current.stamp && // 期望版本和当前版本一致
((newReference == current.reference && //如果当前pair的引用或者时间戳已经和新的一样,就不会走 `||`后面的一部分了
newStamp == current.stamp) || //就是已经设置过,不重复设置
casPair(current, Pair.of(newReference, newStamp)));
}
```
ABA问及解决方法demo
public class ABADemo {
private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
System.out.println("=========================下面是ABA问题======================================");
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
//先暂停1秒 保证完成ABA
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
},"t2").start();
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("=========================下面是解决ABA问题的方法======================================");
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
//暂停1秒钟t3线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 第2次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 第3次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
},"t3").start();
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
//保证线程3完成1次ABA
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t 修改成功否"+result+"\t最新版本号"+stampedReference.getStamp());
System.out.println("最新的值\t"+stampedReference.getReference());
},"t4").start();
}
}
=========================下面是ABA问题======================================
true 2019
=========================下面是解决ABA问题的方法======================================
t4 第1次版本号1 值是100
t3 第1次版本号1 值是100
t3 第2次版本号2 值是101
t3 第3次版本号3 值是100
t4 修改成功否false 最新版本号3
最新的值 100