欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

CAS(Compare And Swap) 详解

程序员文章站 2022-03-04 19:13:58
...

CAS实例

	class AccountSafe implements Account {
	 	private AtomicInteger balance;
	 	public AccountSafe(Integer balance) {
	 		this.balance = new AtomicInteger(balance);
	 	}
		public Integer getBalance() {
		 	return balance.get();
		}

		public void withdraw(Integer amount) {
		    // 需要不断尝试,直到成功为止
			while (true) {
				// 比如拿到了旧值 1000
				int prev = balance.get();
				
				// 在这个基础上 1000-10 = 990
				int next = prev - amount;

				/*
				 compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值
				 - 不一致了,next 作废,返回 false 表示失败
				 比如,别的线程已经做了减法,当前值已经被减成了 990
				 那么本线程的这次 990 就作废了,进入 while 下次循环重试
				 - 一致,以 next 设置为新值,返回 true 表示成功
				 */
				if (balance.compareAndSet(prev, next)) {
					break;
		 		}
			 }
		 		// 可以简化为下面的方法
		 		// balance.addAndGet(-1 * amount);
		 }
	}

过程分析

CAS(Compare And Swap) 详解

CAS和volatile的关系

在java层面是原子类,原子类的底层原来是通过CAS实现的。。查看AtomicInteger的源码,因为要使每个线程get()的都是最新的值(即主内存的值),所以需要对该字段使用volatile关键字修饰。

    private volatile int value;

    /**
     * Gets the current value.
     *
     * @return the current value
     */
    public final int get() {
        return value;
    }

CAS的底层原理

其实 CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交换】的原子性。

为什么无锁效率高

  • 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。打个比喻
  • 线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速… 恢复到高速运行,代价比较大
  • 但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
  • 总结: 使用多核cpu的支持

CAS 的特点

结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。

  • CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
  • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
  • CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思
    • 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
    • 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响