ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

原子累加器及其原理-JUC-并发编程(Java)

2021-09-30 09:05:51  阅读:184  来源: 互联网

标签:JUC 缓存 Java 2000000 cells 累加器 Cell cost new


1、原子整数累加和原子累加器性能比较

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class Test01 {
    public static void main(String[] args) {
        for(int i = 0; i < 10; i++) {
            demo(() -> new AtomicLong(0), AtomicLong::incrementAndGet);
        }
        for (int i = 0; i < 10; i++) {
            demo(LongAdder::new, LongAdder::increment);
        }
    }

    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
        T adder = adderSupplier.get();
        List<Thread> ts = new ArrayList<>();
        // 4个线程,没个线程累加500000次
        for (int i = 0; i < 4; i++) {
            ts.add(new Thread(() -> {
                for (int j = 0; j < 500000; j++) {
                    action.accept(adder);
                }
            }));
        }
        long start = System.nanoTime();
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(adder + " cost: "+ (end - start)/1000_000);
    }
}

// 多次执行测试结果
2000000 cost: 53
2000000 cost: 50
2000000 cost: 45
2000000 cost: 46
2000000 cost: 46
2000000 cost: 46
2000000 cost: 46
2000000 cost: 46
2000000 cost: 46
2000000 cost: 46
2000000 cost: 12
2000000 cost: 9
2000000 cost: 8
2000000 cost: 6
2000000 cost: 6
2000000 cost: 6
2000000 cost: 5
2000000 cost: 6
2000000 cost: 5
2000000 cost: 5

可以发现原子累加器性能明显提升,为什么呢?

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

2、LongAddr原理(源码)分析

2.1、LondAddr关键字段

要分析原理呢,我们先来看下源码。

LongAddr类几个关键的字段

/**
     * Table of cells. When non-null, size is a power of 2.
     */
    transient volatile Cell[] cells;

    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     */
    transient volatile long base;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating Cells.
     */
    transient volatile int cellsBusy;

  • Cell[] cells:累加单元数组,懒惰初始化
  • base:基础值,如果没有竞争,则用cas累加这个字段
  • cellsBusy:在cells创建或者扩容时,置为1,表示加锁;默认0表示未加锁

2.2、cas锁原理

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j(topic = "c.LockCas")
public class LockCas {
    private AtomicInteger state = new AtomicInteger(0);

    public void lock() {
        while (true) {
            if (state.compareAndSet(0, 1)) {
                break;
            }
        }
    }

    public void unlock() {
        log.debug("unlock...");
        state.set(0);
    }
    public static void main(String[] args) {
        LockCas lock = new LockCas();
        new Thread(() -> {
            log.debug("begin...");
            lock.lock();
            try {
                log.debug("lock...");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
            log.debug("begin...");
            lock.lock();
            try {
                log.debug("lock...");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

// 测试结果
2021-09-29 10:28:45.960 DEBUG [t2] c.LockCas - begin...
2021-09-29 10:28:45.960 DEBUG [t1] c.LockCas - begin...
2021-09-29 10:28:45.962 DEBUG [t1] c.LockCas - lock...
2021-09-29 10:28:46.962 DEBUG [t1] c.LockCas - unlock...
2021-09-29 10:28:46.962 DEBUG [t2] c.LockCas - lock...
2021-09-29 10:28:47.962 DEBUG [t2] c.LockCas - unlock...

一旦某个线程加锁,其他线程就会一直循环等待,直到持有锁的线程释放锁。

2.3、缓存行伪共享

Cell类

@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);
            }
        }
    }
  • 注解@sun.misc.Contended 用于防止缓存行伪共享

在这里插入图片描述

CUP与主存速度比较

从CUP到需要的时钟周期
寄存器1 cycle
L13~4 cycle
L210~20 cycle
L340~45 cycle
内存120~240 cycle

因为CUP与内存速度的差异很大,需要靠预读取数据至缓存来提示效率

而缓存以缓存行为单位,没个缓存行对应一块内存,一般是64 byte

缓存的加入会造成数据副本的产生,即内存的同一份数据会缓存在不同核心(多核CUP)的缓存行中

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

在这里插入图片描述

因为cells是数组类型,在内存中是练习存储的,一个Cell为24字节(16字节对象头和8字节的value),因此缓存行可以存下 2个Cell对象。这样问题来了

  • Core-0要修改Cell[0]
  • Core-1要修改Cell[1]

无论谁修改成功,都会导致对方Core缓存行失效

@sun.misc.Contended注解用来解决这个问题,它的原理就是在使用了此注解的对象或者字段前后各家128字节大小的padding,从而让CPU将对象预读至缓存时占用不同的缓存行,这样,不会对方缓存行的失效

在这里插入图片描述

2.4、成员方法

  • 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);
        }
    }

在这里插入图片描述

  • x此处为1

  • 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;
            if ((as = cells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) {
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        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 &&
                                    rs[j = (m - 1) & h] == null) {
                                    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 (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            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);
            }
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

cells数组创建且不为空

在这里插入图片描述

cells未创建或者为空

在这里插入图片描述

符合上述条件

在这里插入图片描述

  • 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;
        }
    
    • 初始值base
    • 变量cells数组
    • 如果不为空,累加对应cell的值

QQ:806797785

仓库地址:https://gitee.com/gaogzhen/concurrent

标签:JUC,缓存,Java,2000000,cells,累加器,Cell,cost,new
来源: https://blog.csdn.net/gaogzhen/article/details/120559877

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有