1. CAS概述
CAS(Compare ans swap/set) 比较并交换,实现并发的一种底层技术。它将预期的值和内存中的值比较,如果相同,就更新内存中的值。如果不匹配,一直重试(自旋)。Java.util.concurrent.atomic包下的原子类都使用了CAS算法
2. CAS原理
CAS具体的操作是将预期的值和内存中真实的值进行比较,如果相同就更新值。如果不相同就重试(自旋)。
CAS是通过Unsafe的compareAndSwap方法实现的,底层实现是CPU原子指令cmpxchg,不会造成数据不一致的问题。
CAS依靠底层硬件实现的无锁原子算法。比synchronized重量级锁性能更好。
3. CAS与自旋锁
3.1 前置知识:原子引用类AtomicReference
将自定义的类型变成原子类,能够进行cas操作。
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { AtomicReference<User> atomicReference = new AtomicReference<>(); User z3 = new User("z3",22); User li4 = new User("li4",28); atomicReference.set(z3); System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString()); System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString()); }
|
3.2 CAS实现一个自旋锁:A线程持有锁,B线程自旋等待直到A释放锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
public class SpinLockDemo { AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() { Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"\t"+"----come in 等待锁"); while (!atomicReference.compareAndSet(null, thread)) {
} System.out.println(Thread.currentThread().getName() + "\t 拿到锁"); }
public void unLock() { Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread,null); System.out.println(Thread.currentThread().getName()+"\t"+"----task over,释放锁..."); }
public static void main(String[] args) { SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> { spinLockDemo.lock(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } spinLockDemo.unLock(); },"A").start();
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> { spinLockDemo.lock();
spinLockDemo.unLock(); },"B").start();
} }
|
核心思想:
- 加锁:开始的时候,内存中的值为NULL,一旦有线程进入,预期值为NULL,和真实值匹配将内存设置为当前线程值。(此时其他线程进来后预期值NULL,但是真实值已经被修改所以会自旋,这为加锁)
- 解锁: 释放锁之后,重新设置内存的值为NULL,让其他线程能够进入从而修改
运行结果:
CAS和synchronized比较
CAS线程不会阻塞,线程一直自旋。
Synchronized会阻塞,会进行线程的上下文切换非常耗费资源。
CAS缺点
循环时间长开销大
CAS如果预测值和真实值不一样,将一直自旋。导致循环CPU开销大。
ABA问题
ABA问题顾名思义就是将线程1在进行CAS操作的时候,另一个线程2已经将A修改为B又快速修改回A,导致一个线程并未发现中间修改过仍能正常进行CAS比较和修改。
ABA问题解决方案
原子时间戳引用:给每次修改都加上一个时间戳(版本号),CAS操作的时候不仅要比较预测值和真实值,还要比较预测版本号和真实版本号。
1 2 3 4
| new AtomicStampedReference().compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)
|
CAS注意事项
CAS只能保证原子性,不能保证变量的可见性。要配合volatile使用,保证共享变量的可见性。
CAS适用于并发量不高,多核CPU的情况。并发量增高,CAS自旋会导致消耗CPU资源。这时候用LongAdder