原子累加器的原理以及Unsafe

原子累加器

JDK中提供了原子累加器类,它们的性能比AotomicLong要高很多

累加器有两个参数:

1:Supplier:提供者 无中生有()->结果 提供累加对象

2:comsumer:消费者 一个参数没有(参数)->void 操作方法

public class com {
    public static void main(String[] args) {
        demo(

                ()->new AtomicLong(0),
                (adder) -> adder.getAndIncrement()
        );
         demo(

                ()->new LongAdder(),
                adder -> adder.increment()
        );
    }

    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action){
        T adder = adderSupplier.get();
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 4;i++){
            ts.add(new Thread(() -> {
                action.accept(adder);
            }));
        }
        long start = System.nanoTime();
        ts.forEach(t -> t.start());
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
    }
}

性能提升的原理:它实际上是在竞争的时候,设置多个累加单元,线程1累加Cell[0],线程2累加Cell[1],最后将觉果汇总。这样它们在累加时操作不同的Cell变量,因此减少了CAS重试失败,从而提高性能。


LongAdder原理——cas锁

LongAdder是并发大师@author douglea(大哥李)的作品,设计的非常精巧

LongAdder类有几个关键域


原理之伪共享

LongAdder中的cells就是累加单元,它的实现如下:

其中@sun.misc.Contended这个注解的作用就防止缓存行的伪共享


那么什么是缓存行的伪共享?

这得从缓存说起,我们先来看一下缓存和内存的速度比较:

因为CPU和内存的速度差异很大,需要靠着预读数据至缓存来提升效率。

而缓存又以缓存行作为单位,每个缓存行对应着一块内存,一般是64byte。

缓存的加入会造成数据副本的产生,就是同一份数据会缓存到不同的缓存行中,一个线程对应一个CPU,对应各一份缓存。

如果CPU要保证数据的一致性,如果某一个CPU核心更改了数据,那么其他CPU核心对应的整个缓存行必须失效

所以当一个Core累加修改成功,那么另一个Core中的缓存就会失效,重新去内存中去获取新的数据,这就会带来效率上的降低。

因为失效是以缓存行为单位的,以前的方法如果失效会多个Cell同时失效,所以后面将采用不同的Cell存在不同的缓存行,那么就可以避免失效问题。

@sun.misc.Contended用来解决这个问题,它的原理是在使用此注解的对象或字段的前后各增加128个字节大小的padding(空白),从而让CPU将对象预读到缓存的时候各自占用不同的缓存行,这样就不会造成对方缓存行的失效。


源码

LongAdder.increment()的源码如下:

然后再来看看longAccumulate方法的源码

如果还没新建Cells累加单元数组,那么会执行以下代码:

如果Cells创建好了,但是当前线程的累加单元还没创建好,会执行以下代码:

如果Cells累加单元数组以及Cell累加单元都已经存在了那么执行以下代码:

最后需要将累加单元数组里的数进行综合,代码如下:


Unsafe

Unsafe对象提供了非常底层的,操作内存,线程的方法,Unsafe对象不能直接调用,只能通过反射获得

CAS和LockSupport中的park,unpark方法中底层使用的都是Unsafe对象


Unsfae对象CAS相关方法

操作示例:

使用Unsafe模拟实现原子整数xuxi

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class Test8 {
    public static void main(String[] args) {

    }

}
class MyAutomicInteger{
    private volatile int  value;
    private static final long valueOffset;
    private static final Unsafe UNSAFE;
    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe)theUnsafe.get(null);
            valueOffset = UNSAFE.objectFieldOffset(MyAutomicInteger.class.getDeclaredField("value"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public int getValue(){
        return value;
    }
    public void decrement(int amount){
        while (true){
            int prev = this.value;
            int after = prev - amount;
            if (UNSAFE.compareAndSwapInt(this,valueOffset,prev, after)){
                break;
            }
        }
    }
    public void increment(int amount){
        while (true){
            int prev = this.value;
            int after = prev + amount;
            if (UNSAFE.compareAndSwapInt(this,valueOffset,prev, after)){
                break;
            }
        }
    }
}

 学习笔记——素材form黑马程序员

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值