Atomic原子类源码解析

前言

原子类都是通过在循环中调用unsafe类提供的CAS(Compare and Swap)操作,以达到无锁的并发及线程安全, CAS是CPU提供的原子性操作

分组
基础数据型 AtomicInteger、AtomicLong、AtomicBoolean
引用型 AtomicReference、AtomicStampedReference、AtomicMarkableReference
数组型 AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
字段更新器 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

基础数据型

AtomicInteger

字段

java 复制代码
/**
 * unsafe实例, Java提供的可直接操作内存地址的类
 */
private static final Unsafe unsafe = Unsafe.getUnsafe();

/**
 * value变量在内存中的相对偏移量, 通过unsafe可以直接操作内存地址中的内容(C语言指针操作)
 */
private static final long valueOffset;

static {
    try {
        // 通过unsafe拿到value变量相对于AtomicInteger类的内存偏移量
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

/**
 * value变量
 */
private volatile int value;

方法

上图是方法调用关系, 可以看到, 底层最终都是通过CAS操作来完成的

unsafe.getAndSet()

java 复制代码
AtomicInteger.java
public final int getAndSet(int newValue) {
    return unsafe.getAndSetInt(this, valueOffset, newValue);
}
java 复制代码
Unsafe.java
public final int getAndSetInt(Object o, long offset, int newValue) {
    int v;
    do {
        // 获取volatile变量, 即保证了可见性的变量, 即可以看到最新值
        v = getIntVolatile(o, offset);
        // 循环调用cas,直至成功
    } while (!compareAndSwapInt(o, offset, v, newValue));
    return v;
}

// cas方法, native
public final native boolean compareAndSwapInt(Object o, long offset,
                                              int expected,
                                              int x);

unsafe.compareAndSwap()

java 复制代码
AtomicInteger.java
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

unsafe.getAndAddInt

java 复制代码
AtomicInteger.java
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
}

public final int getAndAdd(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta);
}

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

public final int decrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}

public final int addAndGet(int delta) {
    return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
java 复制代码
Unsafe.java
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}

lambad表达式作为参数

java 复制代码
public final int getAndUpdate(IntUnaryOperator updateFunction) {
    int prev, next;
    do {
        prev = get();
        next = updateFunction.applyAsInt(prev);
    } while (!compareAndSet(prev, next));
    return prev;
}

public final int updateAndGet(IntUnaryOperator updateFunction) {
    int prev, next;
    do {
        prev = get();
        next = updateFunction.applyAsInt(prev);
    } while (!compareAndSet(prev, next));
    return next;
}

public final int getAndAccumulate(int x,
                                  IntBinaryOperator accumulatorFunction) {
    int prev, next;
    do {
        prev = get();
        next = accumulatorFunction.applyAsInt(prev, x);
    } while (!compareAndSet(prev, next));
    return prev;
}

public final int accumulateAndGet(int x,
                                  IntBinaryOperator accumulatorFunction) {
    int prev, next;
    do {
        prev = get();
        next = accumulatorFunction.applyAsInt(prev, x);
    } while (!compareAndSet(prev, next));
    return next;
}

AtomicLong 实现类似,略

AtomicBoolean

其value值也为int类型, 1表示true、0表示false

java 复制代码
public AtomicBoolean(boolean initialValue) {
    value = initialValue ? 1 : 0;
}

public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;
    int u = update ? 1 : 0;
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

引用型

AtomicReference

实现逻辑与AtomicInteger类似, 只是把变量value改为泛型, 底层通过调用compareAndSwapObject实现

java 复制代码
Unsafe.java
public final native boolean compareAndSwapObject(Object o, long offset,
                                                 Object expected,
                                                 Object x);

AtomicStampedReference

实现具有戳记(Stamp)的原子引用。它通常用于解决CAS操作可能出现的ABA问题

在并发编程中,ABA问题指的是一个变量的值由A变为B,然后再次变为A,但是在这个过程中可能经历了其他线程对这个变量值的修改。如果一个线程在进行CAS操作时,只检查了值是否为A,而没有考虑中间是否有其他线程的修改,那么可能会导致错误的结果

AtomicStampedReference主要用于需要解决ABA问题的场景,例如在一些并发数据结构中,或者需要实现乐观锁的情况下。通过使用标记值,它可以提供更加安全和可靠的原子引用操作

java 复制代码
/**
 * 共享变量
 */
private volatile Pair<V> pair;

private static class Pair<T> {
    final T reference;
    final int stamp;
    private Pair(T reference, int stamp) {
        this.reference = reference;
        this.stamp = stamp;
    }
    static <T> Pair<T> of(T reference, int stamp) {
        return new Pair<T>(reference, stamp);
    }
}

AtomicMarkableReference

标记是一个布尔值,通常用于表示引用的对象是否被标记,例如在垃圾回收或图遍历算法中,标记一个对象表示它已经被访问过

使用方法和AtomicStampedReference类似

java 复制代码
private static class Pair<T> {
    final T reference;
    final boolean mark;
    private Pair(T reference, boolean mark) {
        this.reference = reference;
        this.mark = mark;
    }
    static <T> Pair<T> of(T reference, boolean mark) {
        return new Pair<T>(reference, mark);
    }
}

private volatile Pair<V> pair;

数组型

AtomicIntegerArray

要操作的内存地址从单元素变为数组

数组索引i的内存地址为: base + i << shift

CAS操作和AtomicInteger则一样

java 复制代码
private static final Unsafe unsafe = Unsafe.getUnsafe();
/**
 * 数组的初始内存地址
 */
private static final int base = unsafe.arrayBaseOffset(int[].class);

/**
 * i<<shift可得到偏移量, 通过base + 偏移量即可得到元素的内存地址
 */
private static final int shift;
private final int[] array;

static {
    // scale表示数组元素的字节大小, 此处为4
    int scale = unsafe.arrayIndexScale(int[].class);
    // 4 & 3 = 0, 为了校验scale必须只有最高位为1的值, 即
    if ((scale & (scale - 1)) != 0)
        throw new Error("data type scale not a power of two");
    // numberOfLeadingZeros计算出scale的二进制表示中从最高位开始连续的0的个数
    // scale为4(二进制100) numberOfLeadingZeros(4) = 29
    // 最后shift = 31 - 29 = 2, 之后就可以通过索引i << 2 (等同于i * 4) + base来计算元素的内存地址
    shift = 31 - Integer.numberOfLeadingZeros(scale);
}

AtomicLongArray、AtomicReferenceArray 略

字段更新器

AtomicIntegerFieldUpdater

例如,你有一个类,这个类有一个volatile int字段,你想要原子地更新这个字段,但是你不能(或者不想)改变这个类。这时,你可以使用AtomicIntegerFieldUpdater来实现这个需求。

这个类的一个重要限制是,它只能更新可访问的字段。对于private字段,AtomicIntegerFieldUpdater无法进行更新。此外,字段必须被声明为volatile,否则更新操作无法保证原子性。

实现如下, 抽象类并提供了唯一一个实现, 整个构造器的最终目的还是计算出fieldName字段相对于tclass的偏移量offset

其他方法与之前也都类似

java 复制代码
abstract class AtomicIntegerFieldUpdater
@CallerSensitive
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                          String fieldName) {
    return new AtomicIntegerFieldUpdaterImpl<U>
        (tclass, fieldName, Reflection.getCallerClass());
}
java 复制代码
class AtomicIntegerFieldUpdaterImpl<T> extends AtomicIntegerFieldUpdater<T>

private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private final long offset;
/**
 * if field is protected, the subclass constructing updater, else
 * the same as tclass
 */
private final Class<?> cclass;
/** class holding the field */
private final Class<T> tclass;

AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
                              final String fieldName,
                              final Class<?> caller) {
    final Field field;
    final int modifiers;
    try {
        // 获取field对象
        field = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Field>() {
                public Field run() throws NoSuchFieldException {
                    return tclass.getDeclaredField(fieldName);
                }
            });
        // 获取field的修饰符
        modifiers = field.getModifiers();
        // 保证对字段的访问权限
        sun.reflect.misc.ReflectUtil.ensureMemberAccess(
            caller, tclass, null, modifiers);

        ClassLoader cl = tclass.getClassLoader();
        ClassLoader ccl = caller.getClassLoader();
        if ((ccl != null) && (ccl != cl) &&
            ((cl == null) || !isAncestor(cl, ccl))) {
            sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
        }
    } catch (PrivilegedActionException pae) {
        throw new RuntimeException(pae.getException());
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }

    // 保证字段是int类型
    if (field.getType() != int.class)
        throw new IllegalArgumentException("Must be integer type");

    // 保证字段是volatile修饰
    if (!Modifier.isVolatile(modifiers))
        throw new IllegalArgumentException("Must be volatile type");

    this.cclass = (Modifier.isProtected(modifiers) &&
                   tclass.isAssignableFrom(caller) &&
                   !isSamePackage(tclass, caller))
                  ? caller : tclass;
    this.tclass = tclass;
    // 获取字段偏移量
    this.offset = U.objectFieldOffset(field);
}

AtomicLongFieldUpdater、AtomicReferenceFieldUpdater 略

相关推荐
Victor35640 分钟前
Redis(25)Redis的RDB持久化的优点和缺点是什么?
后端
Victor35641 分钟前
Redis(24)如何配置Redis的持久化?
后端
ningqw8 小时前
SpringBoot 常用跨域处理方案
java·后端·springboot
你的人类朋友8 小时前
vi编辑器命令常用操作整理(持续更新)
后端
胡gh8 小时前
简单又复杂,难道只能说一个有箭头一个没箭头?这种问题该怎么回答?
javascript·后端·面试
一只叫煤球的猫9 小时前
看到同事设计的表结构我人麻了!聊聊怎么更好去设计数据库表
后端·mysql·面试
uzong9 小时前
技术人如何对客做好沟通(上篇)
后端
颜如玉10 小时前
Redis scan高位进位加法机制浅析
redis·后端·开源
Moment10 小时前
毕业一年了,分享一下我的四个开源项目!😊😊😊
前端·后端·开源
why技术11 小时前
在我眼里,这就是天才般的算法!
后端·面试