1.什么是CAS
CAS(Compare And Swap)比较并替换,是线程并发运行时用到的一种技术;
2.CAS作用
乐观锁
3.其他锁机制缺点
在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁。
锁机制存在以下问题:
(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
volatile是不错的机制,但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。
独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
4.CAS实现
CAS需要三个指令,分别是内存位置(JAVA中的内存地址,V),旧的预期值(A)和新值(B)。CAS执行时,当且仅当V符合预期A的时候,新值更新V的值,否则不执行更新,但是最终都会返回V的旧值,上述的处理过程就是一个原子操作。
(图片来源网络)
比如:要将上图中的CPU1要将56更新为57,会比对CPU1拿到的旧值是不是56,如果是就更新;而CPU2拿到的旧值是55≠内存值56,所以更新失败。
JDK1.5之后才可以使用CAS操作,例如Unsafe类中就大量使用CAS操作,还有J.U.C并发包中也有使用。
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);
这里引用一下《深入理解JVM》中的一个例子:
/** * Atomic自增测试 */ public class AtomicTest { public static AtomicInteger race = new AtomicInteger(); public static void increase() { race.incrementAndGet(); } public static final int THREAD_COUNT = 20; public static void main(String[] args) { Thread[] threads = new Thread[THREAD_COUNT]; for (int i = 0; i < THREAD_COUNT; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++) { increase(); } } }); threads[i].start(); } while (Thread.activeCount() > 1) { Thread.yield(); } //race :200000 System.out.println("race :" + race); } }
多线程下,程序执行得到正确的结果,主要是 AtomicInteger的incrementAndGet()方法的原子性。
4."ABA"问题
CAS可以有效的提升并发的效率,但同时也会引入ABA问题。
如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,
并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,
这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。
虽然线程1的CAS操作成功,但是整个过程就是有问题的。比如链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
所以JAVA中提供了AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记(版本号)来标识对象是否有过变更。
总结:
- CAS(Compare And Swap)比较并替换,是线程并发运行时用到的一种技术
- CAS是原子操作,保证并发安全,而不能保证并发同步
- CAS是CPU的一个指令(需要JNI调用Native方法,才能调用CPU的指令)
- CAS是非阻塞的、轻量级的乐观锁