简单理解CAS

简介: 简单理解CAS

@[TOC]

CAS

CAS(Compare And Set)比较交换,是一种无锁算法。即不使用锁的方式来实现多线程同步。由于是无锁的策略,也就是在没有线程被阻塞的情况下实现变量同步,所以也叫非阻塞同步(Non-blocking Synchronization)。

CAS算法:CAS(V, E, N)。

V:要更新的变量。
E:期望值。
N:新值。
当V==E值时,才会将N赋值给V;如果V!=E时,说明已经有别的线程做了更新,当前线程什么都不做(一般是一种自旋的操作,不断的重试-----也是自旋锁)。

基于CAS的线程安全AtomicInteger

package com.thread.atomic;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by shamee-loop on 2020/12/19.
 */
public class AtomicIntegerDemo {
    
    static AtomicInteger i = new AtomicInteger();
    
    public static class AddThread implements Runnable {

        @Override
        public void run() {
            for (int j = 0; j < 10000; j++) {
                i.incrementAndGet();
            }
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int j = 0; j < 10; j++) {
            threads[j] = new Thread(new AddThread());
        }
        for (int j = 0; j < 10; j++) {
            threads[j].start();
        }
        for (int j = 0; j < 10; j++) {
            threads[j].join();
        }
        System.out.println(i);
    }
}

image.png

最终程序输出为100000。如果是线程不安全的情况下,输出的值应该是<100000的。
先来看AtomicInteger的incrementAndGet()方法实现:
image.png

这里的unsafe顾名思义是一个封装了不安全的操作的类。它是sun.misc包下的。这个类是封装了一些类似指针的操作(我们知道C或者C++的指针操作是不安全的,这也是java去除指针的原因,所以暂且这么理解吧)。
我们再跟进去源码:
image.png

可以看到源码356行,实际上使用了public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);是一个native方法。这里的参数含义:
var1:为给定的对象
var2:对象内的偏移量(其实是一个字段到对象头部的偏移量,可以通过这个偏移量来快速定位字段)
var3:期望值
var4:要设置的值
看到这里,其实就知道了compareAndSwapInt方法内部必然是使用CAS原子指令来完成的。

CAS优点

1、在高并发下,性能比锁好
2、避免了死锁的情况

CAS缺点

1、CPU开销大

这个很好理解,上面提到在V!=E的情况下,当前线程会通过自旋的方式来不断的重试,直到操作成功。如果长时间不成功,必然会给CPU带来非常大的开销。

2、只能保证一个共享变量的原子操作

CAS只对一个共享变量有效,当操作多个共享变量时,CAS无效。JDK1.5开始,添加了AtomicRefrence来保证对象引用之间的原子性。我们可以利用锁或者AtomicRefrence把多个变量放在一个对象里来进行CAS操作。

3、ABA问题

如果变量V的初始值是A,有个线程更新了V的值为B;此时,如果当前线程要读取变量V的时候,又有个线程将V的值改为A,这时候当前线程会误以为V是没有被修改过的(实际上被修改了两次,A->B->A)。这就是ABA问题。

image.png

举个栗子:

package com.thread.atomic;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 贵宾卡充值案例模拟。
 * 当余额不足20的时候,充值20
 * 另一条线程,当金额大于10的时候,消费10
 * Created by shamee-loop on 2020/12/19.
 */
public class AtomicReferenceDemo {

    static AtomicReference<Integer> money = new AtomicReference<>(15);

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Integer m = money.get();
            new Thread() {
                public void run() {
                    while (true) {
                        if (m < 20) {
                            if (money.compareAndSet(m, m + 20)) {
                                System.out.println("余额小于20,充值成功。余额=" + money.get());
                                break;
                            }
                        } else {
                            System.out.println("余额大于20,无需充值");
                            break;
                        }
                    }
                }
            }.start();

            new Thread() {
                public void run() {
                        while (true) {
                            Integer m = money.get();
                            if (m > 10) {
                                System.out.println("余额大于10");
                                if (money.compareAndSet(m, m - 10)) {
                                    System.out.println("消费10元,余额=" + money.get());
                                    break;
                                }
                            } else {
                                System.out.println("余额不足");
                                break;
                            }
                        }
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                }
            }.start();
        }
    }
}

程序输出:
image.png

这里多充值了一次20。原因就是账户余额被反复修改,修改后值等于原来的值,误以为没有被修改过,所以导致CAS无法正确判断当前数据状态。

ABA问题解决

1、版本号机制

对象内部多维护一个版本号,每次操作的同时版本号+1;CAS原子操作时,不只是判断值的状态,也判断版本号是否等于原来的版本号;就算值相等,版本号不等,也判断为被线程修改过。

2、带有时间戳的对象引用 AtomicStampReference
package com.thread.atomic;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 贵宾卡充值案例模拟。
 * 当余额不足20的时候,充值20
 * 另一条线程,当金额大于10的时候,消费10
 * Created by shamee-loop on 2020/12/19.
 */
public class AtomicReferenceDemo {

    static AtomicStampedReference<Integer> money = new AtomicStampedReference<>(15, 0);

    public static void main(String[] args) {
        Integer stamp = money.getStamp();
        for (int i = 0; i < 3; i++) {
            Integer m = money.getReference();
            new Thread() {
                public void run() {
                    while (true) {
                        if (m < 20) {
                            if (money.compareAndSet(m, m + 20, stamp, stamp + 1)) {
                                System.out.println("余额小于20,充值成功。余额=" + money.getReference());
                                break;
                            }
                        } else {
                            System.out.println("余额大于20,无需充值");
                            break;
                        }
                    }
                }
            }.start();

            new Thread() {
                public void run() {
                        while (true) {
                            Integer m = money.getReference();
                            Integer stamp = money.getStamp();
                            if (m > 10) {
                                System.out.println("余额大于10");
                                if (money.compareAndSet(m, m - 10, stamp, stamp + 1)) {
                                    System.out.println("消费10元,余额=" + money.getReference());
                                    break;
                                }
                            } else {
                                System.out.println("余额不足");
                                break;
                            }
                        }
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                }
            }.start();
        }
    }
}

程序输出:
image.png

类似版本号机制,这里对象内部不仅维护了对象值,还维护了一个时间戳。当对应的值被修改时,同时更新时间戳。当CAS进行比较时,不仅要比较对象值,也要比较时间戳是否满足期望值,两个都满足,才会进行更新操作。
image.png

这是内部的实现方式。

相关文章
|
存储 资源调度 安全
H3C CAS系列 一、CAS初认识
对于虚拟化,可能第一时间大家想到的是虚拟机,而对于虚拟机大家可能第一时间想到的就是我们大多数人都可能比较熟悉的VMware系列产品,比如常用VMware Workstation Pro 、VMware esxi。 而今天我带大家一起认识一款我们国产的虚拟化软件 H3C CAS。
1554 0
|
1月前
|
算法 调度 数据安全/隐私保护
什么是CAS锁
什么是CAS锁
20 0
|
2月前
|
Java API
CAS的超~详细介绍
CAS的超~详细介绍
|
3月前
|
存储 算法 Java
|
4月前
|
算法
原子操作CAS
原子操作CAS
20 0
|
10月前
CAS问题
CAS问题
|
5月前
|
算法 Java 关系型数据库
CAS
本文主要讲解java中cas的概念及原理
37 0
|
10月前
什么是 CAS? CAS 有哪些缺点?ABA 问题是什么?
什么是 CAS? CAS 有哪些缺点?ABA 问题是什么?
79 0
|
12月前
|
安全 API
对CAS的理解
对CAS的理解
|
算法 Java 容器
CAS乐观锁的实现与问题
Compare and Swap,简称CAS,是一种通过乐观锁保证变量操作原子性的机制。Java的原子变量内部就是通过CAS指令来实现的。
160 0
CAS乐观锁的实现与问题