首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java原子类的实现原理 && CAS的使用以及缺陷

Java原子类的实现原理 && CAS的使用以及缺陷

作者头像
用户11935701
发布2025-12-16 08:55:20
发布2025-12-16 08:55:20
50
举报

CAS

CAS(Compare-And-Swap)是一种原子操作,广泛用于并发编程中实现无锁(lock-free)数据结构,它的核心功能是比较并交换。

代码语言:javascript
复制
boolean CAS(address, old_value, new_value) {
	 if (&address == old_value) {
		 &address = new_value;
		 return true;
	 }
	 return false;
}
  1. 读取阶段: CAS操作会原子性地读取内存中的当前值(称为expectedold_value),同时记录该内存位置的值(这个操作是原子的,不会被中断)。
  2. 比较阶段: 在准备写入新值前,会检查当前内存值&address内部的值,是否仍然等于之前读取的old_value
    • 如果内存值未变(等于old_value),说明内存的值在这个过程中并没有被修改,则进入写入阶段。
    • 如果内存值已变(不等于old_value),说明内存的值在这个过程中被修改过了,则立即返回false(表示失败)。
  3. 写入阶段(仅当比较成功时): 原子性地将新值(new_value)写入内存,并返回true(表示成功)。
  4. 返回值含义
    • true:内存值未被其他线程修改,且新值写入成功。
    • false:内存值已被其他线程修改,操作失败(此时寄存器中的old_value已过时)。
原子类的实现

既然这个操作是原子性的,那么他对于我们有什么用呢?它最大的作用就是来实现原子类。

上述我们提到过,他可以进行比较和交换操作,同时我们可以用它来判定他是否有被修改过,那我们可以使用这个方式来进行多线程下的增加数值操作:

代码语言:javascript
复制
while ( CAS(value, oldValue, oldValue+1) != true) {
 	oldValue = value;
 }

在高并发状态下,如果这个值被多个线程同时修改就会产生线程安全问题,最明显的现象就是这个值和我们理想的结果并不一样。

而如果我们使用CAS来进行这个操作,他会根据你的当前内存真实值和之前识别的旧值oldValue是否一致,来判断你的真实值有没有被修改过,而如果是被修改过后的,那么我们的oldvalue也需要进行相应更新,因为我们要交换的值是根据oldvalue来进行判断的。

这样的操作可以保证每次增加的值都是“有效”的,而不会造成线程安全问题。

原子类的使用

原子类的增加机理就是通过上述方式实现的,使用原子类的目的是避免加锁,在Java中提供了一些原子类的封装。

它们被封装在java.util.concurrent.atomic下(非完整版):

原子类

描述

AtomicBoolean

一个可自动更新的 boolean 值。

AtomicInteger

一个可自动更新的 int 值。

AtomicIntegerArray

元素可原子更新的 int 数组。

AtomicIntegerFieldUpdater<T>

基于反射的工具类,用于原子更新指定类的 volatile int 字段。

AtomicLong

一个可自动更新的 long 值。

AtomicLongArray

元素可原子更新的 long 数组。

AtomicLongFieldUpdater<T>

基于反射的工具类,用于原子更新指定类的 volatile long 字段。

他们的使用方法很简单,例如AtomicInteger增减方法

方法名

返回值

说明

等价操作

getAndIncrement()

int

原子递增 1,返回旧值

i++

getAndDecrement()

int

原子递减 1,返回旧值

i--

getAndAdd(int delta)

int

原子增加 delta,返回旧值

-

incrementAndGet()

int

原子递增 1,返回新值

++i

decrementAndGet()

int

原子递减 1,返回新值

--i

addAndGet(int delta)

int

原子增加 delta,返回新值

-

基于cas实现自旋锁

基于 CAS 实现更灵活的锁, 获取到更多的控制权.。

伪代码:

代码语言:javascript
复制
public class SpinLock {
	 private Thread owner = null;
	 public void lock(){
		 while(!CAS(this.owner, null, Thread.currentThread())){
		 }
	 }
	 public void unlock (){
	 	this.owner = null;
	 }
}
  1. 通过 CAS 看当前锁是否被某个线程持有.
  2. 如果这个锁已经被别的线程持有, 那么就⾃旋等待.
  3. 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.

CAS缺陷

ABA问题

ABA问题是面试时的常见问题,我们知道CAS的判定机理是判断之前读取到的值和内存里面真实的值进行比较,而如果此时出现一个特殊的情况,他在将A变成B后,又将B变回了A,这时它的判断就会出现误判,而这种情况我们就称之为ABA问题。

虽然他在一般情况下不会对我们造成危害,但是他在特殊情景下会对我们造成危害。

例如: 你的名字叫做小明,今天来到银行取取钱,但是你今天手抖了在取钱的时候多按了一次,还好银行是使用CAS来进行增减(假设!),它在第二次扣款时发现内存值和oldvalue值是不一致的,所以仅扣除了一次。

你不由得感叹CAS的伟大。

第二天,你又来取钱了,你今天又手抖了一次,取了两次100元,原本CAS是可以避免这个情况的,但是今天你的朋友此时此刻“恰巧”给你转了100元,使你的金额又变回了原样,CAS在第二次判断的时候呢发现你的钱没有变化,又给你扣了一次,这时你发现你只取出来100但是实际上少了200。

你此时想对CAS说mmp了。

如何避免?

我们知道ABA问题是由于数值又改变回来导致的,那么我们可以设置一个只增/减的值来当做判断依据,例如设置一个版本号,每一次进行更新的时候+1。

此时你可能想到时间这个单位,他是只增的,但是我们并不建议使用这个,因为时间会出现"润秒"事件,感兴趣的可以自行了解。

感谢各位的观看Thanks♪(・ω・)ノ,如果觉得满意的话留个关注再走吧。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CAS
    • 原子类的实现
    • 原子类的使用
    • 基于cas实现自旋锁
  • CAS缺陷
    • ABA问题
    • 如何避免?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档