Java CAS操作

Published: 19 Dec 2018 Category: java

一、引子

在java语言里,什么是悲观锁、乐观锁?

  • synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。
  • CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

可以简单地认为:加锁是一种悲观策略,无锁是一种乐观策略。
因为对于加锁的并发程序来说,它们总是认为每次访问共享资源时总会发生冲突,因此必须对每一次数据操作实施加锁策略。而无锁则总是假设对共享资源的访问没有冲突,线程可以不停执行,无需加锁,无需等待,一旦发现冲突,无锁策略则采用一种称为CAS的技术来保证线程执行的安全性。

二、CAS操作

CAS是Compare-and-swap(比较与替换)的简写,是一种有名的无锁算法。

执行函数:CAS(V,E,N)
- V表示要更新的变量
- E表示预期值
- N表示新值

通俗的理解就是CAS操作需要我们提供一个期望值E,当期望值与当前线程的变量V的值相同时(V.value==E),说明还没线程修改该值,当前线程可以进行修改成N,也就是执行CAS操作;但如果期望值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作。

因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题

在java中,我们主要分析Unsafe类,因为所有的CAS操作都是它来实现的,而在Unsafe类中这些方法也都是native方法。

在Java中无锁操作CAS基于以下3个方法实现:

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);

原子更新基本类型主要包括3个类:

AtomicBoolean:原子更新布尔类型
AtomicInteger:原子更新整型
AtomicLong:原子更新长整型

CAS可以理解成一个无阻塞多线程争抢资源的模型。

package com.demo.reentrantLock;

import java.util.concurrent.atomic.AtomicBoolean;

public class AtomicBooleanTest implements Runnable{
    public static AtomicBoolean exits = new AtomicBoolean(true);
    public static void main(String[] args) {
        AtomicBooleanTest abd = new AtomicBooleanTest();
        Thread t1 = new Thread(abd);
        Thread t2 = new Thread(abd);
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("begin run");
        System.out.println("real " + exits.get());
        if(exits.compareAndSet(true,false)){
            System.out.println(Thread.currentThread().getName() + "  " + exits.get() );
            exits.set(true);
        }else{
            run();
        }
    }
}

输出结果:

begin run
real true
Thread-1  false
begin run
real true
Thread-0  false

这里无论怎么运行,Thread-1、Thread-0都会执行if=true条件,而且还不会产生线程脏读脏写。

cas

这个图中重最要的是compareAndSet(true,false)方法要拆开成compare(true)方法和Set(false)方法理解,是compare(true)是等于true后,就马上设置共享内存为false,这个时候,其它线程无论怎么走都无法走到只有得到共享内存为true时的程序隔离方法区。 但是这种得不到状态为true时使用递归算法是很耗cpu资源的,所以一般情况下,都会有线程sleep。

三、总结

CAS操作即使没有锁,同样知道其他线程对共享资源操作影响,并执行相应的处理措施。同时从这点也可以看出,由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说无锁操作天生免疫死锁。

这篇文章并没有展开讲compareAndSet底层调用的是unsafe.compareAndSwapInt方法,因为这是native方法,很多人都会展开找源码,最后也只找到是调用CPU方法,没讲到具体用法,如果只用compareAndSet(true,false)举例则更加简单。 这种无阻塞式的多线程操作数据,在大并发情况下,是一笔非常可观的性能提升,所以,如果在大并发或多线程性能要求高的情况下有更加好的技术选型,可以参考这种底层实现。

REF

java基础:java CAS操作