CAS
CAS原理简介
CAS的全称是Compare And Swap,中文翻译成比较并交换。是实现并发算法时常用到的一种技术。它包含了三个操作数:内存位置、预期原值及更新值。执行CAS操作的时候,将内存位置的值与预期原值进行比较。如果相匹配,那么处理器会自动将该位置的值更新为新值;如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。
CAS有三个操作数,位置内存值V,旧的预期值A,要修改的更新值B。当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或者重来。当他重来重试的这种行为称为自旋。

Demo案例
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
// 比较并交换,期望值是5,修改为2022 true 2022
System.out.println(atomicInteger.compareAndSet(0, 2022) + "\t" + atomicInteger.get());
// 比较并交换,期望值是5,修改为2022 false 2022
System.out.println(atomicInteger.compareAndSet(0, 2022) + "\t" + atomicInteger.get());
}
}
CAS是如何保证原子性
硬件级别保证
CAS是JDK提供的非阻塞原子性操作。它通过硬件保证了比较-更新的原子性。他是非阻塞的且自身具有原子性,也就是说这玩意效率更高且通过硬件保证,说明可靠性。 CAS是一条CPU原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。执行该指令的时候,会判断当前系统是否为多核系统,如果是就给总线程加锁
,只有一个线程会对总线程加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU独占的,比起用synchronized的重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会比较好。
CAS的底层原理是什么?谈谈你对UnSafe的理解
Unsafe

Unsafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因此Java中CAS操作的执行依赖于Unsafe类的方法。
注意:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的所有方法都直接调用操作系统底层资源执行相应任务。
我们知道i++是线程不安全的,那AtomicInteger.getAndIncrement()如何保证原子性?
AtomicInteger类主要利用CAS+volatile和native方法来保证原子操作,从而避免synchronized的高开销,执行效率大为提升:

CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
原子引用
AtomicReference类是JUC包下的一个类,用于解决原子引用问题。它提供了一些原子操作,如getAndSet、compareAndSet等。JDK中提供了4种原子引用类型,分别是AtomicInteger原子整型。那我们是否可以自定义其他的原子类型呢?如AtomicBook、AtomicOrder等。
package com.weavernorth.juc.base.cas;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author Mr Wang
* @version 1.0
* @Date 2024-10-08 11:02
* @Description 自定义原子引用
**/
class User{
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<>();
User z3 = new User("z3", 22);
User li4 = new User("li4", 28);
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString());
}
}
CAS与自旋锁
什么是自旋锁(spinlock)
CAS是实现自旋锁的基础,CAS利用CPU指令保证了操作的原子性,以达到锁的效果,至于自旋锁—字面意思自己旋转。是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
自己实现一个自旋锁(SpinLockDemo)
/**
* @author Mr Wang
* @version 1.0
* @Date 2024-10-08 11:09
* @Description
* 题目: 实现一个自旋锁,复习CAS的思想
* 自旋锁的好处:循环比较获取没有类似wait的阻塞
* 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来发现当前有线程持有锁,
* 所以只能通过自旋等待,直到A释放锁后B随后抢到
**/
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t --------come in");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void unLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "\t --------task over,unLock.........");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.lock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unLock();
}, "A").start();
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.lock();
spinLockDemo.unLock();
}, "B").start();
}
}
CAS的两大缺点
- ABBA问题:CAS操作不是原子性的,在多线程的情况下,可能会出现ABBA问题。
- 循环时间长,开销很大
如果CAS失败,会一直进行尝试,如果CAS长时间一直不成功,可能会给CPU带来很大开销
ABA问题的产生
CAS算法实现一个重要前提需要提取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。 比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值变成了B,然后线程2又将V位置的数据变成A,这时候线程1进行CAS操作发现内存中仍然是A,预期ok,然后线程1操作成功--------尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的 可以使用版本号时间戳原子引用(AtomicStampedReference)来解决ABA的问题
AtomicStampedReference引用的入门
单线程代码
class Book {
private int id;
private String bookName;
public Book(int id, String bookName) {
this.id = id;
this.bookName = bookName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
}
public class AtomicStampedReferenceDemo {
public static void main(String[] args) {
Book javaBook = new Book(1, "javaBook");
AtomicStampedReference<Book> atomicStampedReference = new AtomicStampedReference<>(javaBook, 1);
System.out.println(atomicStampedReference.getReference() + "\t" + atomicStampedReference.getStamp());
Book mysqlBook = new Book(2, "mysqlBook");
boolean b;
b = atomicStampedReference.compareAndSet(javaBook, mysqlBook, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(b + "\t" + atomicStampedReference.getReference() + "\t" + atomicStampedReference.getStamp());
b = atomicStampedReference.compareAndSet(mysqlBook, javaBook, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(b + "\t" + atomicStampedReference.getReference() + "\t" + atomicStampedReference.getStamp());
}
}
多线程情况下演示AtomicStampedReference解决ABA问题
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author Mr Wang
* @version 1.0
* @Date 2024-10-08 13:07
* @Description 多线程情况下演示AtomicStampedReference解决ABA问题
**/
public class ABADemo {
// 没有携带版本号的原子引用类
static AtomicInteger atomicInteger = new AtomicInteger(100);
// 定义一个带有版本号的原子引用类,可以解决ABA的问题
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
abaNoHappen();
}
private static void abaNoHappen() {
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号: " + stamp);
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t" + "2次版本号: " + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t" + "3次版本号: " + atomicStampedReference.getStamp());
}, "t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号: " + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = atomicStampedReference.compareAndSet(100, 200, stamp, stamp + 1);
System.out.println(b + "\t" + atomicStampedReference.getReference() + "\t" + atomicStampedReference.getStamp());
}, "t4").start();
}
private static void abaHappen() {
new Thread(() -> {
atomicInteger.compareAndSet(100, 101);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInteger.compareAndSet(101, 100);
}, "t1").start();
new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(100, 2023) + "\t" + atomicInteger.get());//true 2023
}, "t2").start();
}
}
2. 原子操作类
2.1 基本类型原子类
- AtomicInteger:整型原子类
- AtomicBoolean:布尔型原子类
- AtomicLong:长整型原子类
2.1.1 常用API介绍
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
2.1.2 案例使用
class MyNumber {
AtomicInteger atomicInteger = new AtomicInteger();
public void addPlusPlus() {
atomicInteger.getAndIncrement();
}
}
public class AtomicIntegerDemo {
public static final int SIZE = 5;
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
MyNumber myNumber = new MyNumber();
for (int i = 1; i < SIZE; i++) {
new Thread(()->{
try {
for (int j = 1; j < 100; j++) {
myNumber.addPlusPlus();
}
} finally {
countDownLatch.countDown();
}
}, String.valueOf(i)).start();
}
// 等待上面的50个线程全部计算完成后再去获得最终值
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t result:"+myNumber.atomicInteger.get());
}
}
2.2 数组类型原子类
- AtomicIntegerArray:整型数组原子类
- AtomicLongrArray:长整型数组原子类
- AtomicReferenceArray:用类型数组原子类
2.2.1 常用API
public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
2.2.2 使用案例
public class AtomicIntegerArrayDemo {
public static void main(String[] args) {
//AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1, 2, 3, 4, 5});
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
for (int i = 0; i < atomicIntegerArray.length(); i++) {
System.out.println(atomicIntegerArray.get(i));
}
System.out.println();
int tempInt = 0;
tempInt = atomicIntegerArray.getAndSet(0, 1122);
System.out.println(tempInt + "\t" + atomicIntegerArray.get(0));
tempInt = atomicIntegerArray.getAndIncrement(0);
System.out.println(tempInt + "\t" + atomicIntegerArray.get(0));
}
}
2.3 引用类型原子类
- AtomicReference :引用类型原子类
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。解决了修改过几次
- AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来。解决了是否修改过,它的定义就是将标记戳简化为true/false,类似于一次性筷子
2.3.1 案例演示
public class AtomicMarkableReferenceDemo {
static AtomicMarkableReference markableReference = new AtomicMarkableReference(100,false);
public static void main(String[] args) {
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t1 默认标识: false
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
markableReference.compareAndSet(100, 1000, marked, !marked);//t2 默认标识: false
}, "t1").start();
new Thread(() -> {
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t2 t2线程CASResult:false
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = markableReference.compareAndSet(100, 2000, marked, !marked);
System.out.println(Thread.currentThread().getName() + "\t" + "t2线程CASResult:" + b);
System.out.println(Thread.currentThread().getName() + "\t" + markableReference.isMarked());//t2 true
System.out.println(Thread.currentThread().getName() + "\t" + markableReference.getReference());//t2 1000
}, "t2").start();
}
}
2.4 对象的属性修改原子类
- AtomicIntegerFieldUpdater:原子更新对象中int类型字段的值
- AtomicLongFieldUpdater:原子更新对象中Long类型字段的值
- AtomicReferenceFieldUpdater:原子更新对象中引用类型字段的值
2.4.1 使用目的
以一种线程安全的方式操作非线程安全对象内的某些字段

2.4.2 使用要求
- 更新的对象属性必须使用public volatile修饰符
- 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性
2.4.3 使用案例
AtomicIntegerFieldUpdater使用案例
class BankAccount {
public volatile int money = 0;
AtomicIntegerFieldUpdater<BankAccount> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");
public void transferMoney(BankAccount bankAccount) {
atomicIntegerFieldUpdater.getAndIncrement(bankAccount);
}
}
/**
* 以一种线程安全的方式操作非线程安全对象的某些字段
* 需求:
* 10个线程
* 每个线程转账1000
* 不实用synchronized关键字,尝试使用 AtomicIntegerFieldUpdater
*/
public class AtomicIntegerFieldUpdaterDemo {
public static void main(String[] args) throws InterruptedException {
BankAccount bankAccount = new BankAccount();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 1000; j++) {
bankAccount.transferMoney(bankAccount);
}
} finally {
countDownLatch.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + '\t' + "result: " + bankAccount.money);
}
}
AtomicReferenceFieldUpdater案例演示
/**
* 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作.
* 要求只能被初始化一次,只有一个线程操作成功
*/
class MyVar {
public volatile Boolean isInit = Boolean.FALSE;
AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");
public void init(MyVar myVar) {
if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
System.out.println(Thread.currentThread().getName() + "\t" + "--------------start init ,need 2 secondes");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "--------------over init");
} else {
System.out.println(Thread.currentThread().getName() + "\t" + "--------------已经有线程进行初始化工作了。。。。。");
}
}
}
public class AtomicReferenceFieldUpdaterDemo {
public static void main(String[] args) {
MyVar myVar = new MyVar();
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
myVar.init(myVar);
}, String.valueOf(i)).start();
}
}
}
2.5 原子操作增强类
- DoubleAccumulator:一个或多个变量,它们一起保持运行double使用所提供的功能更新值
- DoubleAdder:一个或多个变量一起保持初始为零double总和
- LongAccumulator:一个或多个变量,一起保持使用提供的功能更新运行的值long ,提供了自定义的函数操作
- LongAdder:一个或多个变量一起维持初始为零long总和(重点),只能用来计算加法,且从0开始计算
2.5.1 常用API
- void add(long x):将当前的value加x
- void increment(): 将当前的value加1
- void decrement(): 将当前的value减1
- long sum():返回当前的值,特别注意,在没有并发更新value的情况下,sum会返回一个精确值,在存在并发的情况下,sum不保证返回精确值
- void reset():将value重置为0,可用于替代重新new一个LongAdder,但此方法只可以在没有并发更新的情况下使用
- long sumThenReset():返回当前的值,并且重置为0
2.5.2 使用案例
/**
* @author Mr Wang
* @version 1.0
* @Date 2024-10-09 14:01
* @Description
* 需求:
* 50个线程,每个线程100W次,求总点赞数
**/
class ClickNumber {
int number = 0;
public synchronized void clickBySynchronized() {
number++;
}
AtomicLong atomicLong = new AtomicLong(0);
public void clickByAtomicLong() {
atomicLong.getAndIncrement();
}
LongAdder longAdder = new LongAdder();
public void clickByLongAdder() {
longAdder.increment();
}
LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
public void clickByLongAccumulator() {
longAccumulator.accumulate(1);
}
}
public class AccumulatorCompareDemo {
public static final int _1W = 10000;
public static final int THREAD_NUMBER = 50;
public static void main(String[] args) throws InterruptedException {
ClickNumber clickNumber = new ClickNumber();
long StartTime;
long endTime;
CountDownLatch countDownLatch1 = new CountDownLatch(THREAD_NUMBER);
CountDownLatch countDownLatch2 = new CountDownLatch(THREAD_NUMBER);
CountDownLatch countDownLatch3 = new CountDownLatch(THREAD_NUMBER);
CountDownLatch countDownLatch4 = new CountDownLatch(THREAD_NUMBER);
StartTime = System.currentTimeMillis();
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickBySynchronized();
}
} finally {
countDownLatch1.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch1.await();
endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickBySynchronized: " + clickNumber.number);
StartTime = System.currentTimeMillis();
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByAtomicLong();
}
} finally {
countDownLatch2.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch2.await();
endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByAtomicLong: " + clickNumber.atomicLong.get());
StartTime = System.currentTimeMillis();
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByLongAdder();
}
} finally {
countDownLatch3.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch3.await();
endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAdder: " + clickNumber.longAdder.sum());
StartTime = System.currentTimeMillis();
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
try {
for (int j = 1; j <= 100 * _1W; j++) {
clickNumber.clickByLongAccumulator();
}
} finally {
countDownLatch4.countDown();
}
}, String.valueOf(i)).start();
}
countDownLatch4.await();
endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAccumulator: " + clickNumber.longAccumulator.get());
}
}
评论区