美团终面:CAS确定完全不需要锁吗?

CAS咱们都知道,这是一项乐观锁技能,是Compare And Swap的简称,顾名思义便是先比较再替换。

尽管他叫乐观锁,可是咱们都知道它是不需要加锁的,在JDK1.5 中的JUC便是建立在CAS之上的。相对于synchronized这种阻塞算法,CAS是非阻塞算法的一种常见完成。所以J.U.C在功能上有了很大的提升。

咱们以java.util.concurrent中的AtomicInteger为例,看一下在不运用锁的情况下是如何确保线程安全的。主要了解getAndIncrement办法,该办法的效果相当于 ++i 操作:

public class AtomicInteger extends Number implements java.io.Serializable {
        private volatile int value;  
    public final int get() {  
        return value;  
    }  
    public final int getAndIncrement() {  
        for (;;) {  
            int current = get();  
            int next = current + 1;  
            if (compareAndSet(current, next))  
                return current;  
        }  
    }  
    public final boolean compareAndSet(int expect, int update) {  
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
    }  
}

getAndIncrement采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的成果进行CAS操作,如果成功就返回成果,否则重试直到成功停止。而compareAndSet使用unsafe的compareAndSwapInt办法完成的。

啥是Unsafe呢?

Unsafe是CAS的中心类。因为Java无法直接访问底层操作系统,而是经过本地(native)办法来访问。不过尽管如此,JVM仍是开了一个后门,JDK中有一个类Unsafe,它供给了硬件级别的原子操作。

Unsafe是Java中一个底层类,包含了许多基础的操作,比如数组操作、对象操作、内存操作、CAS操作、线程(park)操作、栅门(Fence)操作,JUC包、一些三方框架都运用Unsafe类来确保并发安全。

Unsafe类供给了硬件级别的原子操作,如CAS原子操作。

可是,咱们有没有想过这样的问题:硬件层面CAS又是如何确保原子性的呢?真的彻底没加锁吗?

拿比较常见的x86架构的CPU来说,其实 CAS 操作通常运用 cmpxchg 指令完成的。

可是为啥cmpxchg 指令能确保原子性呢?主要是有以下几个方面的保证:

  1. cmpxchg 指令是一条原子指令。在 CPU 履行 cmpxchg 指令时,处理器会主动锁定总线,避免其他 CPU 访问共享变量,然后履行比较和交流操作,最后释放总线。

  2. cmpxchg 指令在履行期间,CPU 会主动禁止中止。这样可以确保 CAS 操作的原子性,避免中止或其他干扰对操作的影响。

  3. cmpxchg 指令是硬件完成的,可以确保其原子性和正确性。CPU 中的硬件电路确保了 cmpxchg 指令的正确履行,以及对共享变量的访问是原子的。

所以,在操作系统层面,CAS仍是会加锁的,经过加锁的方式锁定总线,避免其他CPU访问共享变量。

所以,解决并发问题,归根到底还得靠锁!!!