LongAdder的使用

LongAdderr的使用以及使用中出现的问题

JDK1.8新增一个原子性操作类LongAdder,用于代替AtomicLong的功能,因为在非常高并发的请求下,AtomicLong的性能是一个很大的瓶颈,因为AtomicLong采用的CAS算法失败后还是通过无限循环的自旋锁不断的尝试。

AtomicLong的自增取值方法:
缺点: 唯一会制约AtomicLong高效的原因是高并发,高并发意味着CAS的失败几率更高,重试次数更多,越多线程重试,CAS失败几率又越高,变成恶性循环,AtomicLong效率降低

public final long incrementAndGet() {
        for (;;) {//无限循环比较compareAndSet
            long current = get();
            long next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
}

为了解决这个问题,JDK的开发组就创建了LongAdder,性能要高于AtomicLong很多。

LongAdder的increment方法:
缺点: 在统计的时候,如果有并发更新,可能会导致统计数据有些误差

/**
     * Adds the given value.
     *
     * @param x the value to add
     */
    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);
        }
    }

    /**
     * Equivalent to {@code add(1)}.
     */
    public void increment() {
        add(1L);
    }

LongAdder内部维护一个Cell[] as数组,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后当获取当前值时候是把所有变量的值累加后再加上base的值返回的。

   /**
     * Padded variant of AtomicLong supporting only raw accesses plus CAS.
     *
     * JVM intrinsics note: It would be possible to use a release-only
     * form of CAS here, if it were provided.
     */
    @sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

对实现机制感兴趣的同学可以阅读源码实现,本文不做过多讲解,因为底层看不懂,怕误人子弟!

LongAdder的使用:

package com.demo.spring.test.baseThread.atomicDemo;

import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.*;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import java.util.concurrent.atomic.LongAdder;

public class LongAdderDemo {

    public static void main(String[] args) {
        int corePoolSize = 4;
        int maxmumPoolSize = 8;
        long keepAliveTime = 0L;
        BlockingQueue blockingQueue = new ArrayBlockingQueue(100);
        ThreadFactory factory = new CustomizableThreadFactory("ThreadName=");
        RejectedExecutionHandler handler = new CallerRunsPolicy();

        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(corePoolSize,maxmumPoolSize,keepAliveTime,TimeUnit.SECONDS,
                blockingQueue,factory,handler);

        //设置线程的并发量
        final Semaphore semaphore=new Semaphore(maxmumPoolSize);

        //线程同步类
        final CountDownLatch countDownLatch=new CountDownLatch(maxmumPoolSize);

        LongAdder longAdder = new LongAdder();
        try {
            for(int i  = 0;i<maxmumPoolSize;i++){
                poolExecutor.submit(() -> {
                    try {
                        semaphore.acquire();
                        longAdder.increment();
                        System.out.println("线程"+Thread.currentThread().getName()+"的值="+longAdder);
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 当前线程执行完才释放
                    countDownLatch.countDown();
                });
            }

            countDownLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            poolExecutor.shutdown();
        }

        System.out.println("最后="+longAdder);
    }

}

结果:

线程ThreadName=1的值=2
线程ThreadName=4的值=3
线程ThreadName=1的值=4
线程ThreadName=3的值=2
线程ThreadName=1的值=6
线程ThreadName=4的值=5
线程ThreadName=3的值=7
线程ThreadName=2的值=8
最后=8

频繁更新的情况下,出现了两个2的值;这就尴尬了,经过测试,出现的概率还挺高的。原因是啥呢?
是因为我们没有给下面这段代码加锁,多线程执行时候,可能输出的值刚好是被其它线程给修改的值。怎么解决这个问题呢,我们只需加个锁就ok了。

synchronized (LongAdderDemo.class){ // 如果不加锁,下面的输出结果可能会存在重复的值
	longAdder.increment();
	System.out.println("线程"+Thread.currentThread().getName()+"的值="+longAdder);
}
线程ThreadName=2的值=1
线程ThreadName=4的值=2
线程ThreadName=3的值=3
线程ThreadName=1的值=4
线程ThreadName=2的值=5
线程ThreadName=4的值=6
线程ThreadName=3的值=7
线程ThreadName=1的值=8
最后=8
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值