Java多线程系列(四)—CAS操作和Automic原子类

来源:互联网 发布:关联交易 数据库 编辑:程序博客网 时间:2024/05/17 22:54

Java多线程系列(四)—CAS操作和Automic原子类

由于线程安全需要保证原子性和可见性,而volatile关键字修饰的变量仅能保证可见性不能保证原子性,因此像i++这种非原子操作就是非线程安全的;为了保证原子性,JDK引入了原子操作类,放在在java.util.concurrent.automic包下;

Automic原子类底层是通过CAS实现,线程的重试会造成效率较低,JDK8引入了LongAdder和DoubleAdder类通过空间换时间的思想提高并发性能;

个人主页:tuzhenyu’s page
原文地址:Java多线程系列(四)—CAS操作和Automic原子类

(1)CAS操作

1.CAS的定义

  • CAS是乐观锁思想的一种实现,是为了保证一组比较替换操作原子性;当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败, 失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

  • CAS乐观锁的实现与悲观锁的区别在于当资源不用的时候悲观锁会将线程挂起而CAS会继续重试直到资源可用;

2.CAS存在的问题(ABA)

  • CAS操作是进行比较替换,容易出现ABA问题;如果一个变量的初始值是A,一个线程准备将A改为了B,在这期间又有其他线程将A改回了B,又将B改回A,这个时候CAS会认为中间没有发生变化,实际上是已经是不同的这就出现了ABA问题;

  • ABA问题的解决办法:JDK引入了解决ABA问题的Automic原子类AutomicStampedReference,使用时间戳版本号来进行标记;

  • 在高并发的情况下会出现CAS频繁碰撞,碰撞会造成赋值失败继续重试,越多线程重试,CAS失败几率又越高,变成恶性循环,造成效率底下等问题;

(2)Automic原子类

1. 原子操作类分类

  • 基本类型:AtomicInteger AtomicLong AtomicBoolean

  • 数组类型:AtomicIntegerArray AtomicLongArray AtomicReferenceArray

  • 引用类型:AtomicReferece AtomicStampedRerence AtomicMarkableReference

  • 对象的属性修改类型:AtomicIntegerFieldUpdater AtomicLongFieldUpdater AtomicReferenceFieldUpdater

2. 原子基本类型(AtomicInteger AtomicLong AtomicBoolean)

  • 基本操作(原子基本类型主要通过CAS硬件原语指令实现操作的原子性):
AtomicLong()    //构造函数AtomicLong(long value)    //构造函数final void set(long value)    //以原子的方式设置当前值为valuefinal long get()    //获取当前值final long decrementAndGet()    //相当于--valuefinal long getAndDecrement()    //相当于value--final long incrementAndGet()    //相当于++valuefinal long getAndIncrement()    //相当于value++

原子操作的关键源码:

public final long incrementAndGet() {    for (;;) {        long current = get();     // 获取AtomicLong当前对应的long值        long next = current + 1;     // 将current加1        if (compareAndSet(current, next))      // 通过CAS函数,更新current的值;            return next;    }}

CAS硬件原语指令

public final boolean compareAndSet(int expect, int update) {    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}

(3)新增LongAdder,DoubleAdder原子类

为了解决AutomicInteger等原子操作类在高并发下的性能问题,JDK8添加了LongAdder和DoubleAdder高并发增强原子类,其基本思想就是对于多个线程对同一个变量操作不相互排斥,而是保存每个线程对变量的修改,在读取变量的时候进行汇总,这样就不会造成线程间的互斥和重试,极大提高想并发性能;

  • LongAdder并发增强原子类的常用方法
add(long x);    //加上一个特定值    increment();    //自增加一decrement();    //自减一
sum();    //对Cell进行汇总计算intValue();    //对sum()结果进行int强制转换floatValue();    //对sum()结果进行float强制转换doubleValue();    //对sum()结果进行double强制转换

LongAdder并发增强原子类实现原理

  • LongAdder增强类常用方法底层都是通过add()方法和sum()方法实现

    • 定义Cell数组用来存储各个线程对变量的修改

    • 如果Cell数组不为空则使用Cell数组进行汇总求和,如果Cell数组为空则调用caseBase()方法进行CAS操作

    • 如果CAS更新失败则创建Cell数组进行分线程更新

public void add(long x) {    Cell[] as; long b, v; int m; Cell a;    if ((as = cells) != null || !casBase(b = base, b + x)) {        boolean uncontended = true;        if (as == null || (m = as.length - 1) < 0 ||            (a = as[getProbe() & m]) == null ||            !(uncontended = a.cas(v = a.value, v + x)))            longAccumulate(x, null, uncontended);    }}
  • 如果Cell数组未创建则进入caseBase()执行CAS操作指令
final boolean casBase(long cmp, long val) {    return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);}
  • 在执行intValue(),floatValue(),doubleValue()等方法时会调用sum()方法对各个线程的操作进行汇总
public long sum() {    Cell[] as = cells; Cell a;    long sum = base;    if (as != null) {        for (int i = 0; i < as.length; ++i) {            if ((a = as[i]) != null)                sum += a.value;        }    }    return sum;}
  • LongAdder这样的处理方式是有坏处的,分段操作必然带来空间上的浪费,可以空间换时间;

  • casBase操作保证了在低并发时,不会立即进入分支做分段更新操作,因为低并发时,casBase操作基本都会成功,只有并发高到一定程度了,才会进入分支,所以,Doug Lea对该类的说明是: 低并发时LongAdder和AtomicLong性能差不多,高并发时LongAdder更高效

总结

  • CAS操作是一种乐观锁思想的实现,相比于悲观锁因为省去了加锁解锁的过程,具有更高的效率;

  • 为了保证操作的原子性,JDK引入了Automic原子类底层基于CAS操作,基本过程就是比较替换,如果失败则进行重试直至成功;

  • 为了解决Automic原子类在高并发的情况下效率底下的问题,JDK引入了LongAdder类和DoubleAdder类,通过分线程本地操作的思想避免了线程之间的碰撞,提高了操作效率;

原创粉丝点击