1.Compare And Set
示例程序
package com.mydemo; import java.util.concurrent.atomic.AtomicInteger; public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(5); System.out.println(atomicInteger.compareAndSet(5,2019)+"\t current data: "+atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(5,2019)+"\t current data: "+atomicInteger.get()); } }
2.执行结果
true current data: 2019 false current data: 2019
3.CAS底层原理
进入AtomicInteger源代码
部分代码
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
进入getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
UnSafe.getAndAddInt()源码解释:
var1 AtomicInteger对象本身。
var2 该对象值得引用地址。
var4 需要变动的数量。
var5是用过var1,var2找出的主内存中真实的值。
用该对象当前的值与var5比较:
如果相同,更新var5+var4并且返回true,
如果不同,继续取值然后再比较,直到更新完成。
CAS指令
CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。
当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
4.CAS缺点
循环时间长开销很大
// ursafe.getAndAddInt public final int getAndAddInt(Object var1, long var2, int var4){ int var5; do { var5 = this.getIntVolatile(var1, var2); }while(!this.compareAndSwapInt(varl, var2, var5,var5 + var4)); return var5; }
我们可以看到getAndAddInt方法执行时,有个do while,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
5.CAS引出来ABA问题
CAS会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
package com.mydemo; import java.util.concurrent.atomic.AtomicReference; public class User { String userName; int age; @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", age=" + age + '}'; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public User(String userName, int age) { this.userName = userName; this.age = age; } } class AtomicReferenceDemo { public static void main(String[] args) { User z3 = new User( "z3",22); User li4 = new User("li4" ,25); AtomicReference<User> userAtomicReference = new AtomicReference<>(); userAtomicReference.set(z3); System.out.println(userAtomicReference.compareAndSet(z3,li4)+"\t"+userAtomicReference.get().toString()); System.out.println(userAtomicReference.compareAndSet(li4,z3)+"\t"+userAtomicReference.get().toString()); System.out.println(userAtomicReference.compareAndSet(z3,li4)+"\t"+userAtomicReference.get().toString()); } }
执行结果
true User{userName='li4', age=25} true User{userName='z3', age=22} true User{userName='li4', age=25}
6.ABA问题解决办法
AtomicStampedReference版本号原子引用
原子引用 + 新增一种机制,那就是修改版本号(类似时间戳),它用来解决ABA问题。
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; public class ABADemo { /** * 普通的原子引用包装类 */ static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); // 传递两个值,一个是初始值,一个是初始版本号 static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1); public static void main(String[] args) { System.out.println("============以下是ABA问题的产生=========="); new Thread(() -> { // 把100 改成 101 然后在改成100,也就是ABA atomicReference.compareAndSet(100, 101); atomicReference.compareAndSet(101, 100); }, "t1").start(); new Thread(() -> { try { // 睡眠一秒,保证t1线程,完成了ABA操作 TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 把100 改成 101 然后在改成100,也就是ABA System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get()); }, "t2").start(); / try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } / System.out.println("============以下是ABA问题的解决=========="); new Thread(() -> { // 获取版本号 int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp); // 暂停t3一秒钟 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 传入4个值,期望值,更新值,期望版本号,更新版本号 atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "\t 第二次版本号" + atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "\t 第三次版本号" + atomicStampedReference.getStamp()); }, "t3").start(); new Thread(() -> { // 获取版本号 int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp); // 暂停t4 3秒钟,保证t3线程也进行一次ABA问题 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + "\t 修改成功否:" + result + "\t 当前最新实际版本号:" + atomicStampedReference.getStamp()); System.out.println(Thread.currentThread().getName() + "\t 当前实际最新值" + atomicStampedReference.getReference()); }, "t4").start(); } }
输出结果
============以下是ABA问题的产生========== true 2019 ============以下是ABA问题的解决========== t3 第一次版本号1 t4 第一次版本号1 t3 第二次版本号2 t3 第三次版本号3 t4 修改成功否:false 当前最新实际版本号:3 t4 当前实际最新值100
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!