抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文学习了CAS相关的原理和操作。

环境

Windows 10 企业版 LTSC 21H2
Java 1.8

1 背景

1.1 线程安全

在并发编程中很容易出现并发安全的问题,比如多个线程执行i++操作。

最常用的方法是通过加锁操作,但是由于加锁操作采用的是悲观锁策略,并不是特别高效的一种解决方案。

除此之外,也可以使用java.util.concurrent.atomic包提供的原子类,使用CAS采用类似乐观锁的策略去更新数据,解决并发安全问题。

1.1 乐观锁和悲观锁

悲观锁假设对共享资源的访问都会发生冲突,当有一个线程访问资源,其他线程就必须等待,所以采用了加锁的方式解决冲突。

乐观锁假设对共享资源的访问是没有冲突的,线程可以同时访问资源。如果遇到冲突的话,就使用CAS技术重复检测冲突,直到没有冲突才能访问资源。

2 简介

CAS(Compare and Swap,比较并交换)技术属于乐观锁技术(又称为无锁技术),指当多个线程同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试更新。

3 原理

CAS包含了三个参数:

  • V:表示要读写的内存位置。
  • A:表示旧的预期值。
  • B:表示新值。

在更新时比较V中存储的值和A的值:

  • 如果二者不同,说明可能是其他线程做了更新,那么当前线程什么都不做,执行返回或者重试。
  • 如果二者相同,将V中存储的值设为B的值,并返回A的值。

当多个线程同时使用CAS更新变量时,只有一个线程会成功,其余线程均会失败,但失败的线程不会被挂起,而是不断的再次循环重试,这个过程也称为自旋。

正是基于这样的原理,CAS即时没有使用锁,也能发现其他线程对当前线程的干扰,从而进行及时的处理。而且因为没有使用锁,也就不会造成死锁。

4 核心

Unsafe类是特殊的类,位于sun.misc包中,其内部方法可以像C的指针一样直接操作内存,CAS依赖Unsafe类的方法。

Unsafe类是CAS的核心类,基于该类可以直接操作特定的内存数据,该类的所有方法都是native修饰的,并且该类提供了底层的CAS机制。

5 缺点

5.1 CPU开销较大

在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。

解决办法目前没有,只能通过让JVM支持处理器提供的PAUSE指令来减轻CPU的压力。

5.2 多个共享变量不能保证原子性

CAS所保证的只是一个变量的原子性操作,而不能保证多个共享变量的原子性。

解决办法是使用锁机制或者使用AtomicReference类封装多个共享变量。

5.3 ABA问题

CAS判断变量操作成功的条件是V中存储的值和A的值是一致的,如果V中存储的值原来是A,被改成了B,又被改回为A,经过了两次的修改,但是CAS还是认为该变量从来没被修改过。

解决办法是使用AtomicStampedReference类通过追加版本号或时间戳同时判断原值是否改变。

评论