《Java修炼指南:高频源码解析》阅读笔记一Java并发包原子类

JUC包提供了一系列常用数据结构的原子类,这些类位于Java.util.concurrent.atomic包下,这些类都是使用CAS实现的,相比使用锁的方式在性能上有了很大的提升,可以再高并发的场景下,保证线程安全的同时,以更简单、高效的方式操作一个共享变量。

一、AtomicLong

类似类:AtomicIntegerAtomicIntegerArrayAtomicBooleanAtomicLongArrayAtomicReferenceAtomicReferenceArray

原子变量操作类主要使用Unsafe类的CAS原子更新方法实现原子递增或递减操作。
主要属性:

 // setup to use Unsafe.compareAndSwapLong for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    /**
     * Records whether the underlying JVM supports lockless
     * compareAndSwap for longs. While the Unsafe.compareAndSwapLong
     * method works in either case, some constructions should be
     * handled at Java level to avoid locking user-visible locks.
     */
    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();

    /**
     * Returns whether underlying JVM supports lockless CompareAndSet
     * for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.
     */
    private static native boolean VMSupportsCS8();

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicLong.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile long value;

二、LongAddr

在JDK1.8中新增了四个高性能原子类,这四个类使用了分段锁的思想,将不同的线程映射到不同的数据段上去更新将这些段的值相加就能得到最终的值

  • LongAccumulator:Long类型的聚合器,需要传入一个Long类型的二元操作,可以用来计算各种集合操作,包括加乘;
  • DoubleAccumulator:Double类型聚合器,需要传入一个DOuble类型的二元操作,可以用来计算各种聚合操作,包括加乘等;
  • LongAdder:Long类型累加器,是LongAccumulator的特例,只能用来计算加法,从0开始计算;
  • DoubleAdder:Double类型累加器,是DoubleAccumulator的特例,只能用来计算加法,从0开始计算;

在这里插入图片描述

LongAddr继承自Striped64Striped64是一个抽象类,用来实现累加功能,它是实现高并发累加的工具类。在Striped64定义了三个变量,LongAddr真实值其实就是将base的值和cells数组元素值累加。

AtomicLong使用内部变量value保存着实际的long值,所有线程的读写操作都是针对一个变量进行的,也就说在高并发环境下,N个线程会同时竞争一个热点value变量的读写
LongAddr的基本思路就是分散并发冲突的热点。在没有线程竞争的情况下,将要累加的数累计额到一个热点base基础值上;当有线程竞争的时候,将热点分散到cells数组中,不同线程映射到不同的数组位置上,每个线程对应位置中的元素进行CAS递增或者递减操作,这样就可以从一个热点分散成多个热点,发生并发冲突的概率就会小很多。
LongAddr中,与其说是LongAddr,不如说是Striped64中有三个重要的变量:

  • base:在没有线程竞争的时候,执行递增、递减操作会直接CAS更新该值;
  • cells:为Cell类型数组,在有线程竞争时,每个线程会映射到对应的cells[i]的位置上进行CAS操作;
  • cellsBusy:在cells初始化时、进行扩容时、槽进行初始化时使用,保证cells变量的线程安全。0:锁空闲;1:锁被占用

如果要获取LongAddr当前值,需要将base和cells数组中所有元素累加起来。
其他属性:

// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
// base字段的内存偏移量
private static final long BASE;
// cellsBusy字段的内存偏移量
private static final long CELLSBUSY;
// Thread的threadLocalRandomProbe字段内存偏移量,可以看成是线程的hash值,在计算
// 线程映射cells引用位置时使用。
private static final long PROBE;

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;
        // value偏移量
        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);
            }
        }
    }

LongAddr中最主要的方法就是addsum

public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    // cells不等于null,即将值直接累加到cells中,它认为之前发生过冲突,此时产生并发冲突的概率
    // 也比较高,直接将值累加到cells数组中
    // casBase,对于base属性的CAS操作,如果操作成功则直接加到base中,失败则下一步
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        // 不冲突,这个指的是cells中元素是否冲突
        boolean uncontended = true;
        // as == null || (m = as.length - 1) < 0:cells还未初始化
        // (a = as[getProbe() & m]) == null:当前线程映射到cells中相应位置还未初始化
        // !(uncontended = a.cas(v = a.value, v + x))):更新值到映射cell,成功直接退出
        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);
    }
}
// toString默认调用该方法
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;
}
// 刷新实例为0
public void reset() {
    Cell[] as = cells; Cell a;
    base = 0L;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                a.value = 0L;
        }
    }
}

sum在并发场景中,只能得到一个近似值,如果想得到绝对准确的值还是需要全局锁的。这也是LongAddr不能完全代替LongAtomic的原因之一

Striped64中最主要的方法:longAccumulate

/**
x:累加的值
fn:操作函数
wasUncontended:是否冲突过
**/
final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
    	// h为0,则重新获取
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            wasUncontended = true;
        }
    	// 冲突后标记为true,并且rehash
        boolean collide = false;                // True if last slot nonempty
    	// 自旋循环,CAS累加值直至成功
        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 r = new Cell(x);   // Optimistically create
                        // 初始化cell时,加锁
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                // 初始化cell
                                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;
                }
                // Continue after rehash
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                // 将值累加到cells中,成功退出
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                // 当数组大于NCPU(Runtime.getRuntime().availableProcessors())CPU内核数
                // cells过时
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                // 标记冲突,并且rehash后自旋重试
                else if (!collide)
                    collide = true; // 下次自旋在下面分支进行扩容,不过在扩容前会再次尝试一下CAS
                // rehash后依旧无法更新,则获取锁进行扩容
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as) {      // Expand table unless stale
                            // 动态扩大cells至两倍
                            Cell[] rs = new Cell[n << 1];
                            // 将原来cells中的值,移到相同的位置
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                // 刷新当前线程的hash
                h = advanceProbe(h);
            }
            // cells还未初始化且加锁成功
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                // 是否初始化成功
                // 只有一种可能失败,那就是加锁后,cells已经被其他线程抢先初始化
                boolean init = false;
                try {                           // Initialize table
                    // 判断是否被其他线程初始化
                    if (cells == as) {
                        // cells中数量一定是2的幂数
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    // 释放锁
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            // cells还未初始化并且加锁未成功,这表示其他线程正在初始化,尝试更新base
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

cellsBusy在三种情况下进行加锁:

  1. cells数组初始化
  2. cells数组扩容迁移
  3. cells数组中某个槽初始化

注:本文为《Java修炼指南:高频源码解析》阅读笔记

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java面试八股文:高频面试题与求职攻略一本通》是一本旨在帮助Java求职者提升面试竞争力的参考书籍。本书以高频面试题为主要内容,以求职攻略为辅助,全面涵盖了Java面试的各个方面。 首先,本书对Java基础知识进行了系统梳理。涵盖了Java的核心概念、面向对象思想、多线程、集合框架等关键知识点。通过对这些基础知识的深入解析和举例,读者能够更好地理解并掌握Java语言的精髓。 其次,本书还深入剖析了Java虚拟机(JVM)和垃圾回收机制。对于面试中经常涉及的内存模型、垃圾回收算法等内容进行了详细解读,帮助读者从深层次了解Java程序的执行和性能优化。 此外,本书还介绍了Java的常用框架和工具,如Spring、Hibernate、MyBatis等,以及一些Java开发常用的设计模式。为读者提供了在面试中展示自己综合能力的机会,同时也使得读者在实际项目开发中能够更加得心应手。 最后,本书独有的求职攻略部分为读者提供了一系列求职技巧和面试策略。包括简历编写、面试前的准备、面试中的表现技巧等方面的内容,帮助读者提高自己的求职竞争力。 综上所述,《Java面试八股文:高频面试题与求职攻略一本通》是一本综合性的面试备考书籍。通过学习本书,读者能够全面掌握Java面试的要点和技巧,提升自己在竞争激烈的求职市场中的竞争力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值