并发编程之CAS

2022-07-26 17:06:37 浏览数 (1)

引入CAS(compareAndSwap)的原因

CAS即compareAndSwap(比较和交换),引入CAS的主要原因是解决锁带来的性能影响,我们知道在Java中,为了保证变量的原子性,Java提供了一些 锁机制,比如synchronized,Lock等,锁是能够保证变量的原子性,其原理是阻塞线程,当某一个线程正在执行任务时,如果任务加了锁,那么其他线程 进入任务时会被阻塞,这样会导致性能变得很差,在高并发的场景下时极度不推荐加锁操作的,所以引入了CAS。

CAS原理

CAS是JDK提供的原子操作类,它提供了硬件级别的隔离,可以使用Unsafe来操作CAS,Unsafe类下提供了compareAndSwap*操作,我们看出compareAndSwap* 这些方法都是native(本地方法)。

代码语言:javascript复制
    public final native boolean compareAndSwapObject(Object obj, long valueOffset, Object expect, Object update);

    public final native boolean compareAndSwapInt(Object obj, long valueOffset, Object expect, Object update);

    public final native boolean compareAndSwapLong(Object obj, long valueOffset, Object expect, Object update);

compareAndSwap*本地方法有四个参数,第一个object为对象的内存地址,valueOffset代理对象在内存中的偏移量,expect为期望值,update为新值 ,连贯起来解释这个方法的意思,如果对象obj内存偏移量为valueOffset的值等于期望值expect,那么使用新值update替换期望值expect,反之则不做任何操作

CAS ABA问题

当线程1使用CAS修改变量A的值为X,在执行CAS操作之前,线程1先获取变量A,这时线程2获取变量A,然后使用CAS操作更新A为Y,然后又将A更新为X,这时虽然A依然为X, 但是已经不是线程1的那个X了,可能说得有点抽象,那么我们就用一个简单且通俗的经历来说,我们小时候和小伙伴一起玩耍,小伙伴借了我一个玩具,然后后面我给他弄丢了, 突然有一天我们闹矛盾了,然后他叫我还他玩具,我于是买了一个一模一样的给他,但是他却不要,他说:"我不要你这个,我就要我的那个","我就要我的那个其实上升为一个哲学问题了", 那么ABA问题其实就是这个意思,虽然值一模一样,但是已经不是以前那个啦。

解决ABA问题

版本号

解决ABA问题我们可以加一个版本号,其实就是乐观锁的方式,每次更新操作版本号加1 比如线程1需要更新变量A,它会先获取变量A的值和版本号,如图,线程1获取到的版本号为1,此时线程2也获取到A,对其进行更新操作,version 1,此时version=2, 那么这个时候线程1对便两个A进行更新,发现版本和自己自己最初获取的版本号不一样,则证明变量A被修改过,那么就不对其进行更新。

AtomicStampedReference

AtomicStampedReference给每个变量的状态值加上一个时间戳,从来避免ABA问题。

下面我们演示一下ABA问题以及使用AtomicStampedReference解决ABA问题。

使用AtomicInteger

threadA使用compareAndSet对变量atomicInteger进行两次更新,然后threadB对atomicInteger进行更新,输入为true,显然,这是不对的。

代码语言:javascript复制
public class CasTest {
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInteger.compareAndSet(100, 200);
                atomicInteger.compareAndSet(200, 100);
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                Thread.sleep(2000);
                boolean b = atomicInteger.compareAndSet(100, 200);
                System.out.println(b);
            }
        });
        threadA.start();
        threadB.start();
    }
}
使用AtomicStampedReference

使用AtomicStampedReference,每次更新操作都会加上一个时间戳,于是输出为false,因为变量已经不是以前那个变量了,虽然值是一样的

代码语言:javascript复制
public class CasAtomicStampedReferenceTest {
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,0);

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicStampedReference.compareAndSet(100, 200, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()   1);
                atomicStampedReference.compareAndSet(200, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()   1);
            }
        });

        Thread thread1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                Thread.sleep(2000);
                boolean b = atomicStampedReference.compareAndSet(100, 200, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()   1);
                System.out.println(b);
            }
        });

        thread.start();
        thread1.start();
    }
}

关于CAS,我们分享到这里,感谢你的观看,我们下期见!

0 人点赞