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