CAS操作
CAS的定义
CAS的全称为“Compare-And-Swap”,直译就是对比并交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值。其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,如AtomicInteger类便是使用了这些封装后的接口。CAS的简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁。JDK中大量使用了CAS来更新数据而防止加锁(synchronized 重量级锁)来保持原子更新。
public interface Account {// 获取余额Integer getBalance();// 取款void withdraw(Integer amount);/*** 方法内会启动 1000 个线程,每个线程做 -10 元 的操作* 如果初始余额为 10000 那么正确的结果应当是 0*/static void demo(Account account) {List<Thread> ts = new ArrayList<>();long start = System.nanoTime();for (int i = 0; i < 1000; i++) {ts.add(new Thread(() -> {account.withdraw(10);}));}ts.forEach(Thread::start);ts.forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long end = System.nanoTime();System.out.println(account.getBalance()+ " cost: " + (end - start) / 1000_000 + " ms");}public static void main(String[] args) {Account.demo(new AccountCas(10000));}}
前面的学习中,可以使用加锁的方式来保证线程安全,如:
public class AccountLock implements Account{private Integer balance;public AccountLock(Integer balance) {this.balance = balance;}@Overridepublic Integer getBalance() {return balance;}@Overridepublic void withdraw(Integer amount) {balance -= amount;}}
但是,如果不加锁,怎么确保线程安全?其实,线程安全问题就是出在共享变量“Integer balance”身上,Java中其实提供了原子操作的Interger类:AtomicInteger,它可以确保线程安全,如:
public class AccountCas implements Account {private final AtomicInteger balance;public AccountCas(Integer balance) {this.balance = new AtomicInteger(balance);}@Overridepublic Integer getBalance() {return balance.get();}@Overridepublic void withdraw(Integer amount) {while (true) {int prev = balance.get();int next = prev - amount;//compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值// - 不一致了,next 作废,返回 false 表示失败// 比如,别的线程已经做了减法,当前值已经被减成了 990// 那么本线程的这次 990 就作废了,进入 while 下次循环重试// - 一致,以 next 设置为新值,返回 true 表示成功if (balance.compareAndSet(prev, next)) {break;}}// 可以简化为下面的方法// balance.addAndGet(-1 * amount);}}

可以看到,我们没有加锁,依然保证了线程安全。其原因就是在于“compareAndSet”这个方法,其实现就是采用的CAS操作实现的。CAS其实就是“compareAndSet”的缩写,当然,也有另一种说法是“CompareAndSwap”的缩写,不过表达的意思都是一样:在修改共享变量前,会将工作内存的变量和主存的变量进行比较(compare),如果一样就进行修改(Set或Swap),如果不一样则放弃修改并进入下一轮循环(如上面的例子),直到一样为止,示意图如下所示:
这样,就可以保证变量balance的线程安全。
CAS与锁的比较
性能的比较
CAS和加锁可以达到一样的效果,而且通过上面的例子比较,CAS的性能甚至会高于加锁,原因解释如下面的比喻:
无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速… 恢复到高速运行,代价比较大。但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
当前,性能优于加锁是有前提条件的:【适用于线程数少、多核 CPU 的场景下】。如果只有单核CPU,没有多余的CPU核心去维持在高速运转的CAS线程;如果运行的线程很多,可以想到重试必然频繁发生,反而效率会受影响。
实现思想的比较
CAS与volatile
CAS操作的实现其实是需要依赖volatile这个关键字的,因为需要和主存的变量进行比较,这就需要volatile修饰哪个变量了。volatile可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。CAS 必须借助 volatile 才能读取到共享变量的最新值来实现比较并交换的效果。
CAS的一些问题
ABA问题
AtomicStampedReference的使用示例如下:
@Slf4j(topic = "c.main")public class Main {private static AtomicStampedReference<Integer> atomicStampedRef =new AtomicStampedReference<>(1, 0);public static void main(String[] args) {Thread main = new Thread(() -> {System.out.println("操作线程" + Thread.currentThread() + ",初始值 a = " + atomicStampedRef.getReference());int stamp = atomicStampedRef.getStamp(); //获取当前标识别try {Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行} catch (InterruptedException e) {e.printStackTrace();}boolean isCASSuccess = atomicStampedRef.compareAndSet(1, 2, stamp, stamp + 1); //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败System.out.println("操作线程" + Thread.currentThread() + ",CAS操作结果: " + isCASSuccess);}, "主操作线程");Thread other = new Thread(() -> {Thread.yield(); // 确保thread-main 优先执行atomicStampedRef.compareAndSet(1, 2, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);System.out.println("操作线程" + Thread.currentThread() + ",【increment】 ,值 = " + atomicStampedRef.getReference());atomicStampedRef.compareAndSet(2, 1, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);System.out.println("操作线程" + Thread.currentThread() + ",【decrement】 ,值 = " + atomicStampedRef.getReference());}, "干扰线程");main.start();other.start();}}
循环时间长开销大
只能保证一个共享变量的原子操作
13个原子操作类
Atomic包下的原子操作类
Java 从 JDK 1.5 开始提供了 java.util.concurrent.atomic 包(简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。Atoic包下提供了12个类,他们分别可以实现四种原子操作:原子更新基本类型、原子更新数组、原子更新引用、原子更新属性(字段)。
原子更新基本类型

实例代码:
public static void main(String[] args) {AtomicInteger ai = new AtomicInteger(1);System.out.println("ai.get() = " + ai.get());System.out.println("ai.addAndGet(5) = " + ai.addAndGet(5));System.out.println("ai.get() = " + ai.get());System.out.println("ai.compareAndSet(ai.get(), 10) = " + ai.compareAndSet(ai.get(), 10));System.out.println("ai.get() = " + ai.get());System.out.println("ai.getAndIncrement() = " + ai.getAndIncrement());System.out.println("ai.get() = " + ai.get());ai.lazySet(8);System.out.println("ai.lazySet(8)");System.out.println("ai.get() = " + ai.get());System.out.println("ai.getAndSet(5) = " + ai.getAndSet(5));System.out.println("ai.get() = " + ai.get());}
原子更新引用类型

以AtomicReference为例说明:
public class Main {public static AtomicReference<User> atomicUserRef = new AtomicReference<>();public static void main(String[] args) {User user = new User("conan", 15);atomicUserRef.set(user);User updateUser = new User("Shinichi", 17);atomicUserRef.compareAndSet(user, updateUser);System.out.println(atomicUserRef.get().getName());System.out.println(atomicUserRef.get().getOld());}@Data@AllArgsConstructor@NoArgsConstructorstatic class User {private String name;private int old;}}
原子更新字段类

如:
public class Main {//创建一个原子更新器private static final AtomicIntegerFieldUpdater<User> ATOMIC_INTEGER_FIELD_UPDATER =AtomicIntegerFieldUpdater.newUpdater(User.class, "old");public static void main(String[] args) {User user = new User("Tom", 15);//原来的年龄System.out.println(ATOMIC_INTEGER_FIELD_UPDATER.getAndIncrement(user));//现在的年龄System.out.println(ATOMIC_INTEGER_FIELD_UPDATER.get(user));}@Data@AllArgsConstructor@NoArgsConstructorstatic class User {private String name;public volatile int old;}}
原子更新数组

示例代码:
public class Main {static int[] value = new int[]{1, 2};static AtomicIntegerArray ai = new AtomicIntegerArray(value);public static void main(String[] args) {ai.getAndSet(1, 300);System.out.println(ai.get(1));System.out.println(value[1]);}}
Unsafe类
Unsafe类简介
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。这个类尽管里面的方法都是 public 的,但是并没有办法使用它们,JDK API 文档也没有提供任何关于这个类的方法的解释。总而言之,对于 Unsafe 类的使用都是受限制的,只有授信的代码才能获得该类的实例,当然 JDK 库里面的类是可以随意使用的。
如上图所示,Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。CAS的API的详细用法参考文章:
https://blog.csdn.net/muyimo/article/details/121379139
获取Unsafe类
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得。
public class Main {static Unsafe unsafe;static {try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);unsafe = (Unsafe) theUnsafe.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}static Unsafe getUnsafe() {return unsafe;}public static void main(String[] args) {System.out.println(getUnsafe());}}
CAS操作
public class Main {static Unsafe unsafe;static {try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);unsafe = (Unsafe) theUnsafe.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}static Unsafe getUnsafe() {return unsafe;}public static void main(String[] args) throws NoSuchFieldException {Unsafe unsafe = getUnsafe();Field id = Student.class.getDeclaredField("id");Field name = Student.class.getDeclaredField("name");// 获得成员变量的偏移量long idOffset = unsafe.objectFieldOffset(id);long nameOffset = unsafe.objectFieldOffset(name);Student student = new Student();// 使用 cas 方法替换成员变量的值unsafe.compareAndSwapInt(student, idOffset, 0, 20); // 返回 trueunsafe.compareAndSwapObject(student, nameOffset, null, "张三"); // 返回 trueSystem.out.println(student);}}@Dataclass Student {volatile int id;volatile String name;}

因此,利用Unsafe类,可以自己实现一个简单的AtomicInteger类,如:
public class AtomicData {private volatile int data;static final Unsafe unsafe;static final long DATA_OFFSET;static {unsafe = Main.getUnsafe();try {// data 属性在 DataContainer 对象中的偏移量,用于 Unsafe 直接访问该属性DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("data"));} catch (NoSuchFieldException e) {throw new Error(e);}}public AtomicData(int data) {this.data = data;}public void decrease(int amount) {int oldValue;while (true) {if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue - amount)) {return;}}}public int getData() {return data;}}

