Java CAS 看这一篇就够了

CAS

既 compareAndSwap,比较并交换。如果线程的期望值和物理内存的值相等,本次修改成功;如果不相等则失败。

例子:

import java.util.concurrent.atomic.AtomicInteger;

/* CAS:compareAndSwap,比较并交换 */
public class CASDemo {
	public static void main(String[] args) {
		// Integer 原子类对象,设置默认值10
		AtomicInteger atomicInteger = new AtomicInteger(10);
		
		System.out.println("--------第一次修改--------");
		System.out.println("修改结果:"+atomicInteger.compareAndSet(10, 2020));
		System.out.println("当前atomicInteger的值为:"+atomicInteger.get());
		System.out.println("--------第二次修改--------");
		System.out.println("修改结果:"+atomicInteger.compareAndSet(10, 2030));
		System.out.println("当前atomicInteger的值为:"+atomicInteger.get());
	}
}

运行结果:

底层原理:

自旋锁、UnSafe类

CAS 是什么

CAS 的全称为 Compare-And-Swap ,它是一条CPU并发原语。
它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的。


CAS并发原语体现在Java语言中就是sun.misc.UnSafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令。这是一种完全依赖于硬件功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题。

通过 CAS 可以引出来一堆东西,咱们一点一点来。

CAS ----> UnSafe类 ----> CAS 底层思想 ----> ABA ----> 原子引用更新 ----> 如何规避 ABA 问题

Unsafe为什么能够保证线程安全

  1. UnSafe是CAS的核心类,由于Java 方法无法直接访问底层,需要通过本地(native)方法来访问,UnSafe相当于一个后面,基于该类可以直接操作特额定的内存数据。UnSafe类在于rt.jar/sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作的助兴依赖于UNSafe类的方法。注意:UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务
  2. 变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的
  3. 变量value和volatile修饰,保证了多线程之间的可见性.

unSafe.getAndIncrement 方法讲解

var1 AtomicInteger对象本身.
var2 该对象值的引用地址
var4 需要变动的数值
var5 是用过var1 var2找出内存中绅士的值
用该对象当前的值与var5比较
如果相同,更新var5的值并且返回true
如果不同,继续取值然后比较,直到更新完成

假设线程A和线程B两个线程同时执行 getAndAddInt 操作(分别在不同的CPU上):

  1. AtomicInteger 里面的value原始值为3,即主内存中 AtomicInteger 的 value 为3,根据JMM模型,线程A和线程B各自持有一份值为 3 的 value 的副本分别到各自的工作内存。
  2. 线程 A 通过 getIntVolatile(var1,var2) 拿到value值3,这是线程A被挂起。
  3. 线程 B 也通过 getIntVolatile(var1,var2) 拿到 value 值 3,此时刚好线程B没有被挂起并执行 compareAndSwapInt 方法比较内存中的值也是 3 成功修改内存的值为4 线程 B 打完收工一切OK。
  4. 这是线程 A 恢复,执行 compareAndSwapInt 方法比较,发现自己手里的数值和内存中的数字 4 不一致,说明该值已经被其他线程抢先一步修改了,那A线程修改失败,只能重新来一遍了。
  5. 线程 A 重新获取 value 值,因为变量 value 是 volatile 修饰,所以其他线程对他的修改,线程 A 总是能够看到,线程 A 继续执行 compareAndSwapInt 方法进行比较替换,直到成功。

底层汇编

为什么用 CAS 不用 synchronized

因为 synchronized 同时时间内只允许一个线程访问,所以并发量大大下降;而 CAS 底层使用的 Unsafe 类实现汇编指令,并且是原子操作,同时利用自旋锁进行判断,既保证了一致性,有提高的并发量。

CAS 的缺点

1. 循环时间长,开销大

2. 只保证一个共享变量的原子性

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

3. 引来了 ABA 问题

ABA 问题

CAS 会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程 one 从内存位置 V 中取出 A ,这时候另一个线程 two 也从内存中取出 A ,并且线程 two 进行了一些操作将值变成了B,然后线程 two 又将 V 位置的数据变成 A ,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后线程 one 操作成功。
尽管线程 one 的 CAS 操作成功,但是不代表这个过程就是没有问题的。

原子引用 AtomicReference

/* User 实体类 */
class User{
	private String name;
	private int age;
	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}
}
/* 加入默认提供的 Atomic 包装类不够用,则可以使用AtomicReference<> 将类升级为原子类 */
public class AtomicReferenceDemo {
	public static void main(String[] args) {
		User zhangsan = new User("张三",18);
		User lisi = new User("李四",19);
		
		AtomicReference<User> atomicReference = new AtomicReference<>();
		atomicReference.set(zhangsan);
		// 更换张三为李四,ture
		System.out.println(atomicReference.compareAndSet(zhangsan, lisi)+":"+atomicReference.get());
		// 更换张三为李四,false
		System.out.println(atomicReference.compareAndSet(zhangsan, lisi)+":"+atomicReference.get());
	}
}

运行结果:

如何规避 ABA 问题

可以在原子引用的基础之上新增一种机制:类似版本号(时间戳)。

import java.util.concurrent.atomic.AtomicStampedReference;

/* 解决 ABA 问题,引入 AtomicStampedReference 类 */
public class ABADemo {

    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
	public static void main(String[] args) {
		new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 的版本号为:" + stamp);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 );
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 );
        }).start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 的版本号为:" + stamp);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(b); // false
            System.out.println(atomicStampedReference.getReference()); // 100
        }).start();
	}
}
发布了41 篇原创文章 · 获赞 21 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/cong____cong/article/details/104361341