博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
完全看懂CAS之JDK并发包JUC里cas使用volatile变量自旋的乐观锁模式解决多线程单变量同步问题与CAS缺点ABA资源消耗
阅读量:2059 次
发布时间:2019-04-29

本文共 2585 字,大约阅读时间需要 8 分钟。

当谈到cas解决多线程同步问题,看一个人有没有真正理解cas,不是听他说cas就是使用cpu机器指令比较前后值完成设置保证了原子性,这样基本就是百度百出来的,根本没有好好理解cas的真正过程

【cas要点】

  1. cas方法中的同步变量【必须】是volatile类型
  2. cas自旋(死循环),判断值前后变化来保证多线程中的值同步

JUC就是jdk并发包【目录简称

以AtomicInteger内部源码举例,jdk1.8.0_202

AtomicInteger.java    public final int incrementAndGet() {        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;    }    unsafe.java    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;    }

也有的显示源码是这样的

public final int incrementAndGet() {    for (;;) {        int current = get();        int next = current + 1;        if (compareAndSet(current, next))            return next;    }}public final boolean compareAndSet(int expect, int update) {    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}

AtomicInteger会在类的字节码文件加载到jvm时,执行static静态块初始化,取得内部value变量偏移长度

static {        try {            valueOffset = unsafe.objectFieldOffset                (AtomicInteger.class.getDeclaredField("value"));        } catch (Exception ex) { throw new Error(ex); }    }

value是volatile类型,value就是AtomicInteger代表的值

private volatile int value;

然后主要是看cas循环怎么解决多线程变量同步问题,就是怎么解决 i++ 这样的问题的,这样才能真正理解cas的无锁同步机制

循环的过程主要是两步,可以理解为两个原子操作1. 获取当前值【最新】值,volatile属性int current = get(); 或者//var1是实例对象this,var2是valueOffset就是value的偏移长度,这样也可以得到value最新值var5 = this.getIntVolatile(var1, var2); 2. 判断与修改值 含义:通过对象this与变量偏移地址valueOffset取得变量值 //如果与预期expect (就是上面的原子操作取得的值) 一致,说明这两步原子操作中间【没有】被【并发修改】,可以设置为update //与预期expect不一致,进入下次循环。 //说明value被其他线程修改了,为了保证变量同步,应该重新获取一次最新值 //也就是当前线程的这两个原子操作中间被中断,线程挂起,被其他线程执行了cas设置 //通过对象this与变量偏移地址valueOffset可以获取最新被修改后的值 (volatile属性)(这是在cas内部处理) //expect就是第一个原子操作取得的current值, 用于校验这两个原子操作是否被中断,从而保证【多线程单变量同步】 unsafe.compareAndSwapInt(this, valueOffset, expect, update) 或者this.compareAndSwapInt(var1, var2, var5, var5 + var4)

从源码以及我上面的说明可以看出,cas自旋一次的过程是两个原子操作

  1. 没有并发时,就直接cas比较成功,然后设置成功
  2. 有并发时,变量被并发线程修改,会导致cas内部通过偏移地址valueOffset获取的最新值与第一个原子操作的取得值不一致,从而判断出变量已被其他线程修改,为保证多线程共享变量同步,进入下次循环,获取新的值,继续进行一次比较,直到没有被其他线程修改,设置成功。可以看出这里有多线程争用,只有一个线程可以cas成功,其他线程在不断的循环重试cas

CAS问题

  1. 只能保证一个volatile变量的无锁同步
即使使用多个cas设置多个volatile变量,但是不能保证这些多个volatile变量被同时在一起修改的原子性没有原子性,就会出现并发问题,变量值不正确。
  1. ABA问题
虽然CAS保证了两个原子操作的前后的【值】一样,但是不能保证这个值没有被重新设置为【一样】的值比如这个值可能是某个链表的指针,虽然地址是一样,执行同一个地方,但是你不知道,在第一个原子操作后,线程挂起其他线程修改了这个链表后,恰巧把相同的地址放在这里,等你第二个原子操作开始,虽然【值】是一样的但是这个【新值】所指的含义,或者这个【新值】本身的含义不同于原来的【旧值】
  1. 性能问题
从我上面的分析,看出,虽然cas无锁同步,保证了多线程下的单变量同步但当需要cas同一变量的线程太多,因为同一时刻,只有一个线程可以cas成功余下的线程都在循环cas,线程越多,争用越大,循环时间就长,消耗cpu时间

转载地址:http://ayalf.baihongyu.com/

你可能感兴趣的文章