CAS(LongAdder)

不用锁来保护共享变量的线程安全,可以使用cas的原子类
在这里插入图片描述compareSet:比较并设置,内部是原子的

为什么无锁效率高
  • 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而synchronized会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。打个比喻
  • 线程就好像高速跑道上的赛车,高速运行时,速度超快,- -旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速… 恢复到高速运行,代价比较大
    ●但无锁情况下,因为线程要保持运行,需要额外CPU的支持,CPU在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。

所以cas适合在多核cpu下,并且线程数不能超过cpu核数,线程数高了也跑不起来

CAS的特点

结合CAS和volatile可以实现无锁并发,适用于线程数少、多核CPU的场景下q

  • CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。

  • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。

  • CAS体现的是无锁并发(并不是通过加锁的方式的来保护共享资源而是通过不断重试)、无阻塞并发(保持cas线程不断运行),请仔细体会这两句话的意思

  • 因为没有使用synchronized, 所以线程不会陷入阻塞,这是效率提升的因素之一

  • 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响

在这里插入图片描述

累加器

在这里插入图片描述比原子类的性能好,累加器专门用来做累加。

性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加Cel[0],而Thread-l 累加Cel[1]…最后将结果汇总。这样它们在累加时操作的不同的Cell变量,因此减少了CAS重试失败,从而提高性能。

LongAdder

在这里插入图片描述1、Cell在多线程累加的时候用自己线程cell单元,用于减少重试次数。
2、当没有线程竞争的时候,就没有必要创建cell而是使用base。
3、cas是无锁的,通过不断地重试来保证并发,那为什么还表示加锁呢?

总结:
以上三个变量都是呗volatile修饰的,并且都有transient关键字,表示序列化的时候不会被序列化。
在这里插入图片描述

原理之伪共享

其中Cell即为累加单元
在这里插入图片描述Contended:竞争的意思
什么是缓存行伪共享?
在这里插入图片描述读取不同的地方数据所需时间
在这里插入图片描述而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是64 byte (8 个long)
缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中
CPU要保证数据的一致性,如果某个CPU核心更改了数据,其它CPU核心对应的整个缓存行必须失效

在这里插入图片描述在这里插入图片描述因为失效是以缓存行为单位的,要失效就一起失效,为了解决这个问题就有了Contended注解(放置一个缓存行容纳多个cell对象)

原理:
在使用此注解的对象或字段的前后各增加128字节大小的padding,相当于添加空白数据将其填满一行,只让放一个cell对象
在这里插入图片描述

LongAdder源码-add

在这里插入图片描述
代码:

public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        //判断累加单元的数组为空不为空(当没有竞争的时候cells数组为null,当竞争发生了才会去创建cells数组,从而在创建里面的累加单元)
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
            //判断当前线程cell是否创建
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

当累加单元的数组或者累加单元没有创建或者创建失败就会进入longAccumulate
在这里插入图片描述
longAccumulate的实现:

 final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        //死循环
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            //创建了cells数组,但是累加单元不够,用的时候才创建累加单元
            if ((as = cells) != null && (n = as.length) > 0) {
            //获取当前线程有没有对应的累加单元
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       // Try to attach new Cell
                    //创建cell累加单元,它还未放进去cells数组
                        Cell r = new Cell(x);   // Optimistically create
                        //判断别人上锁没,并尝试上锁
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    //检查线程对应的cells的槽位是不是null
                                    rs[j = (m - 1) & h] == null) {
                                    //为空,将刚才自己创建的cell对象填到该槽位上
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)//当槽位不为空,就回到循环入口
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                    //数组存在并且对应的累加单元也已经创建
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;//累加成功就返回,失败进入下面的else if
                else if (n >= NCPU || cells != as)//判断数组的长度是否超过cpu核数
                    collide = false; //防止数组扩容     
                else if (!collide)//超过cpu核数的话,代码会走到这
                    collide = true;
                    //没有超过cpu核数,扩容的逻辑(先判断有没有加锁,再用cas的方式加锁)
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {//扩容逻辑
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];//扩大为原来的2倍
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];//将就数组拷贝到新数组里
                            cells = rs;//新数组替换旧数组
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);//改变线程对应的cell对象,在循环
            }
            //cells数组还不存在得情况(cellsBusy标记位,0标记未加锁,1标记加锁),没有其他线程去改变cells数组,casCellsBusy()以cas的方式将cellsBusy标记位从0改为1
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                //创建cells数组初始化其中的一个累加单元后退出循环
                try {                           // Initialize table
                    if (cells == as) {//判断有没有其他的线程已经把cells数组创建好了,相等表示没有创建好
                        Cell[] rs = new Cell[2];//数组长度为2
                        rs[h & 1] = new Cell(x);//累加单元为1
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;//解锁
                }
                if (init)
                    break;//退出循环
            }
            //在上面的情况cas加锁失败时,用cas去base上累加,成功就返回,失败就回到循环入口
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值